Pigeon.Messaging.Azure.EventHub 2.8.0

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

Pigeon.Messaging

Simple. Fast. Broker-agnostic messaging for .NET.

Build NuGet License: MIT


Pigeon is a lightweight, extensible library for .NET that abstracts integration with messaging systems like RabbitMQ, Kafka, Azure Service Bus, Azure Event Grid, and Azure Event Hub.

Its goal is to simplify publishing and consuming messages through a unified, decoupled API, so you can switch message brokers without rewriting your business logic.


Features

  • Consistent API for multiple message brokers.
  • Fluent configuration through IServiceCollection.
  • Publish and consume workflows with topic and semantic-version support.
  • Raw message publishing when a broker payload should be sent without the default Pigeon wrapper.
  • Routed publishing for broker-native fan-out patterns such as RabbitMQ exchanges, routing keys, queues, and bindings.
  • Consumer discovery through HubConsumer and ConsumerAttribute.
  • Publish and consume interceptors for metadata, tracing, security context, sagas, and other cross-cutting behavior.
  • Configurable topology provisioning to create broker infrastructure on startup, publish, consume, or leave it fully manual.
  • Configurable acknowledgement behavior with manual ack, auto-ack on receive, or ack after a successful handler.
  • Broker adapters that keep business code independent from the transport.
  • In-memory broker for unit tests, examples, and modular monolith scenarios.
  • Mule-backed transactional outbox for durable broker dispatch with retry, recovery, cleanup, and diagnostics.
  • Lightweight core package with adapter packages for each broker.

Pigeon is a good fit for microservices, distributed architectures, and applications that need reliable asynchronous communication without coupling domain code to a specific broker SDK.

Supported Brokers

  • RabbitMQ
  • Kafka
  • Azure Service Bus
  • Azure Event Grid
  • Azure Event Hub
  • In-memory

Supported Frameworks

Pigeon 2.0 supports:

  • .NET 8
  • .NET 9
  • .NET 10

Installation

Install the core package, one broker adapter, and any optional outbox providers you need:

dotnet add package Pigeon.Messaging
dotnet add package Pigeon.Messaging.Rabbit
dotnet add package Pigeon.Messaging.Kafka
dotnet add package Pigeon.Messaging.Azure.ServiceBus
dotnet add package Pigeon.Messaging.Azure.EventGrid
dotnet add package Pigeon.Messaging.Azure.EventHub
dotnet add package Pigeon.Messaging.InMemory
dotnet add package Pigeon.Testing
dotnet add package Pigeon.Messaging.Outbox.EntityFrameworkCore
dotnet add package Pigeon.Messaging.Outbox.InMemory

Quick Start

Configure Pigeon

Register Pigeon in your Program.cs or Startup.cs:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Pigeon.Messaging;
using Pigeon.Messaging.Rabbit;
using System.Text.Json;

var builder = Host.CreateApplicationBuilder(args);

builder.Services
    .AddPigeon(builder.Configuration, config =>
    {
        config.SetDomain("YourApp.Domain")
              .UseRabbitMq(rabbit =>
              {
                  rabbit.Url = "amqp://guest:guest@localhost:5672";
              });
    })
    .ConfigureJsonOptions(options =>
    {
        options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
    });

var app = builder.Build();
await app.RunAsync();

Define a Consumer

Create a message contract and register a handler:

public class HelloWorldMessage
{
    public string Text { get; set; }
}

builder.Services
    .AddPigeon(builder.Configuration, config =>
    {
        config.SetDomain("YourApp.Domain")
              .UseRabbitMq();
    })
    .AddConsumeHandler<HelloWorldMessage>(
        topic: "hello-world",
        version: "1.0.0",
        handler: (context, message) =>
        {
            return Task.CompletedTask;
        });

You can also group related consumers in a HubConsumer and register them by scanning assemblies:

public class CreateUserMessage { }
public class UpdateUserMessage { }
public class UpdateUserV2Message { }

public class UserHubConsumer : HubConsumer
{
    private readonly IAnyService _service;

    public UserHubConsumer(IAnyService service)
    {
        _service = service;
    }

    [Consumer("create-user", "1.0.0")]
    public Task CreateUser(CreateUserMessage message, CancellationToken cancellationToken = default)
    {
        return Task.CompletedTask;
    }

    [Consumer("update-user", "1.0.0")]
    [Consumer("update-user", "1.0.1")]
    public Task UpdateUser(UpdateUserMessage message, CancellationToken cancellationToken = default)
    {
        return Task.CompletedTask;
    }

    [Consumer("update-user", "2.0.0")]
    public Task UpdateUserV2(UpdateUserV2Message message, CancellationToken cancellationToken = default)
    {
        return Task.CompletedTask;
    }
}

Register consumers discovered in an assembly:

builder.Services.AddPigeon(builder.Configuration, config =>
{
    config.ScanConsumersFromAssemblies(typeof(UserHubConsumer).Assembly)
          .UseRabbitMq();
});

Publish a Message

Resolve IProducer and publish a message to a topic:

var producer = app.Services.GetRequiredService<IProducer>();

await producer.PublishAsync(
    new HelloWorldMessage { Text = "Hello, Pigeon!" },
    topic: "hello-world");

Publish a Raw Message

Use raw publishing when you want to send the payload directly to the broker without the default wrapped Pigeon envelope:

await producer.PublishRawAsync(
    new HelloWorldMessage { Text = "Hello, Pigeon!" },
    topic: "hello-world");

Publish Inside an Ambient Transaction

When PublishAsync runs inside a TransactionScope and the transactional outbox is not enabled, Pigeon suppresses the ambient transaction for the direct broker publish by default. This keeps brokers that do not participate in the current transaction from trying to enlist in it:

builder.Services.AddPigeon(builder.Configuration, config =>
{
    config.ConfigurePublishing(publishing =>
    {
        publishing.AmbientTransactionBehavior =
            AmbientTransactionPublishBehavior.SuppressTransaction;
    });
});

Suppressing the transaction means the broker publish is not atomic with the surrounding business transaction. If the message must be consistent with database changes, enable the transactional outbox instead.

Use Throw when you want Pigeon to fail fast if direct broker publishing happens inside an ambient transaction:

config.ConfigurePublishing(publishing =>
{
    publishing.AmbientTransactionBehavior =
        AmbientTransactionPublishBehavior.Throw;
});

Route a Message to Multiple Consumers

Adapters that support broker-side routing can publish one message and deliver it to multiple configured consumers. In RabbitMQ, for example, one publish can target an exchange and routing key while each consumer owns its queue and binding:

config.SetTopologyProvisioningMode(
        TopologyProvisioningMode.OnStartup |
        TopologyProvisioningMode.OnPublish |
        TopologyProvisioningMode.OnConsume)
      .UseRabbitMq(rabbit =>
      {
          rabbit.Url = "amqp://guest:guest@localhost:5672";
          rabbit.Exchange = "orders.exchange";
          rabbit.ExchangeType = "direct";
      });

pigeon.AddConsumeHandler<OrderCreatedMessage>(
    topic: "orders.created",
    version: "1.0.0",
    subscription: "billing.orders.created",
    handler: (context, message) => Task.CompletedTask);

pigeon.AddConsumeHandler<OrderCreatedMessage>(
    topic: "orders.created",
    version: "1.0.0",
    subscription: "audit.orders.created",
    handler: (context, message) => Task.CompletedTask);

await producer.PublishAsync(
    new OrderCreatedMessage(),
    topic: "orders.exchange",
    routingKey: "orders.created",
    version: "1.0.0");

The Rabbit sample includes a runnable end-to-end version with one exchange, one routing key, two queues, and two bindings:

dotnet run --project samples/Pigeon.Messaging.Rabbit.Sample/Pigeon.Messaging.Rabbit.Sample.csproj

Use the In-Memory Broker

Use the in-memory broker for tests, samples, or modular monoliths where messages should stay inside the current process:

builder.Services
    .AddPigeon(builder.Configuration, config =>
    {
        config.UseInMemoryBroker();
    })
    .AddConsumeHandler<OrderCreatedMessage>(
        topic: "orders.created",
        version: "1.0.0",
        subscription: "billing-module",
        handler: (context, message) => Task.CompletedTask)
    .AddConsumeHandler<OrderCreatedMessage>(
        topic: "orders.created",
        version: "1.0.0",
        subscription: "audit-module",
        handler: (context, message) => Task.CompletedTask);

await producer.PublishAsync(new OrderCreatedMessage(), "orders.created");

One publish is delivered to every matching in-memory subscription. The broker is process-local, non-durable, and not distributed, so it is not a replacement for RabbitMQ, Kafka, or Azure brokers between services.

Tests can inspect the broker state:

var broker = serviceProvider.GetRequiredService<IInMemoryBroker>();

Assert.Single(broker.PublishedMessages);
Assert.Equal(2, broker.Deliveries.Count);

Run the in-memory sample:

dotnet run --project samples/Pigeon.Messaging.InMemory.Sample/Pigeon.Messaging.InMemory.Sample.csproj

Test Pigeon Without a Broker

Use Pigeon.Testing when tests need to inspect producers, consumers, message dispatch, metadata, retries, and failure paths without RabbitMQ, Kafka, Azure Service Bus, or a full application host:

services.AddPigeonTesting();
services.AddPigeonTestingConsumers(typeof(CustomersHubConsumer).Assembly);

Publish messages into the in-memory testing transport and dispatch them when the test is ready:

var pigeon = serviceProvider.GetRequiredService<IPigeonTestingTransport>();
var customerId = Guid.NewGuid();

await pigeon.PublishAsync(new CustomerCreatedMessage(customerId));

pigeon.ShouldContainMessage<CustomerCreatedMessage>(
    message => message.CustomerId == customerId);

await pigeon.DispatchPendingAsync();

pigeon.ShouldContainConsumedMessage<CustomerCreatedMessage>(
    message => message.CustomerId == customerId);

PublishAsync uses the real Pigeon producer pipeline, so publish interceptors can enrich the payload before the testing transport captures it:

var message = pigeon.ShouldContainMessage<CustomerCreatedMessage>();
message.Headers["correlation-id"].ShouldBe(correlationId);
message.CorrelationId.ShouldBe(correlationId);

Failure paths can be simulated without touching broker SDKs:

pigeon.FailNext<CustomerCreatedMessage>(new TimeoutException());

await pigeon.PublishAsync(new CustomerCreatedMessage(customerId));
await pigeon.DispatchPendingAsync();

pigeon.ShouldContainDeadLetterMessage<CustomerCreatedMessage>();
pigeon.ShouldHaveConsumerFailure<CustomerCreatedMessage>();

External test hosts can expose a thin wrapper over the adapter-friendly registration:

services.AddPigeonTestingAdapter(typeof(CustomersHubConsumer).Assembly);

Use the In-Memory Outbox

Use the in-memory outbox provider for tests and samples that need the real Pigeon outbox pipeline without a database. This provider uses Mule's in-memory durable action engine under the Pigeon outbox API:

builder.Services.AddPigeon(builder.Configuration, config =>
{
    config.UseInMemoryBroker();
    config.UseInMemoryOutbox();
});

The provider stores durable actions in the current process and exposes IInMemoryOutbox for assertions:

var outbox = serviceProvider.GetRequiredService<IInMemoryOutbox>();

Assert.Single(outbox.Messages);

It is process-local and non-durable. Use Pigeon.Messaging.Outbox.EntityFrameworkCore for production durability.

Configure Topology Provisioning

Pigeon defaults to manual topology provisioning, so infrastructure is expected to already exist unless configured otherwise. You can combine provisioning modes when your topology is partly known at startup and partly dynamic at runtime:

config.SetTopologyProvisioningMode(
    TopologyProvisioningMode.OnStartup |
    TopologyProvisioningMode.OnPublish |
    TopologyProvisioningMode.OnConsume);
  • Manual: Pigeon only publishes and consumes.
  • OnStartup: creates known topology when the app starts.
  • OnPublish: creates publish topology when a dynamic publish route appears.
  • OnConsume: creates consume topology when a dynamic consumer appears.

Pigeon keeps an in-memory registry of provisioned topology so the same queue, topic, subscription, exchange, or binding is not recreated on every publish or consume.

For high-throughput publishers, avoid first-message topology latency by warming known publish routes during startup:

config.SetTopologyProvisioningMode(
    TopologyProvisioningMode.OnStartup |
    TopologyProvisioningMode.OnPublish);

config.PreProvisionPublishRoutes(
    PublishingRoute.ForExchange("events", "orders.created"),
    PublishingRoute.ForExchange("events", "orders.cancelled"));

OnPublish is useful for dynamic routes, but known hot-path routes should be pre-provisioned when possible.

Configure Consumer Acknowledgements

Consumer acknowledgements are configured globally. The default is Manual, which means Pigeon does not ack automatically:

config.ConfigureConsumerExecution(execution =>
{
    execution.AcknowledgementMode = MessageAcknowledgementMode.Manual;
    execution.HandlerTimeout = TimeSpan.FromSeconds(30);
});

Available acknowledgement modes:

  • Manual: the handler controls acknowledgement through ConsumeContext.CompleteAsync() or ConsumeContext.FailAsync(...).
  • OnReceive: the adapter uses broker auto-ack behavior where available.
  • OnHandlerSuccess: Pigeon acknowledges only after the handler completes successfully.

By default, Pigeon does not cap consumer dispatch concurrency based on CPU cores. Delivery is broker-driven and handlers are dispatched as messages arrive. If an application needs to protect a dependency such as a database, HTTP API, or downstream service, set an explicit limit:

config.ConfigureConsumerExecution(execution =>
{
    execution.MaxConcurrency = 128;
    execution.QueueCapacity = 10_000;
    execution.PrefetchCount = 128;
});

When MaxConcurrency and QueueCapacity are both null or less than 1, Pigeon does not create an internal dispatch queue; broker adapters dispatch directly into the consumer pipeline. If either setting is configured, Pigeon enables the internal dispatch queue so it can apply concurrency control and backpressure before handlers run.

RabbitMQ prefetch uses ConsumerExecution.PrefetchCount when configured. If PrefetchCount is not configured but MaxConcurrency is configured, RabbitMQ derives prefetch from MaxConcurrency. With OnReceive, RabbitMQ uses auto-ack and Pigeon does not apply QoS.

Azure Service Bus and Azure Event Grid consumption map the same common settings into Azure processor options: MaxConcurrency becomes MaxConcurrentCalls, and PrefetchCount becomes the processor PrefetchCount. Kafka concurrency remains partition-driven by the Kafka consumer group and assigned partitions; Pigeon does not fake queue-style parallelism on top of Kafka partitions.

For productive high-throughput defaults, opt in explicitly:

config.ConfigureHighThroughputConsumers();

That helper sets a bounded MaxConcurrency, bounded QueueCapacity, PrefetchCount, and keeps the existing handler timeout unless one is provided:

config.ConfigureHighThroughputConsumers(
    concurrencyMultiplier: 8,
    queueCapacityMultiplier: 100,
    handlerTimeout: TimeSpan.FromMinutes(2));

Consumer backlog can be inspected through IConsumerExecutionDiagnostics:

var snapshot = diagnostics.GetSnapshot();

Console.WriteLine(snapshot.QueuedMessages);
Console.WriteLine(snapshot.ActiveHandlers);
Console.WriteLine(snapshot.AverageQueueWait);

Broker adapters accept consumed messages through an async consume contract, so bounded QueueCapacity applies async backpressure instead of blocking broker callback threads with sync-over-async calls.

Manual acknowledgement works from consumer methods and hub consumers:

pigeon.AddConsumeHandler<HelloWorldMessage>(
    topic: "hello-world",
    version: "1.0.0",
    handler: async (context, message) =>
    {
        await DoWorkAsync(message);
        await context.CompleteAsync();
    });

Access the Current Consume Context

Use IConsumeContextAccessor when application services need to read the current ConsumeContext without receiving it directly as a method argument:

public class CurrentMessageTenantProvider
{
    private readonly IConsumeContextAccessor _consumeContextAccessor;

    public CurrentMessageTenantProvider(IConsumeContextAccessor consumeContextAccessor)
    {
        _consumeContextAccessor = consumeContextAccessor;
    }

    public string GetTenantId()
    {
        var context = _consumeContextAccessor.ConsumeContext;
        return context?.GetMetadata<string>("tenantId");
    }
}

ConsumeContext is only available while Pigeon is running consume interceptors or the consumer handler for the current message. Outside a consume pipeline, the accessor returns null.

Configure the Transactional Outbox

The transactional outbox plugs into the producer pipeline. PublishAsync still runs publish interceptors in the current scope, builds the final WrappedPayload, and then stores that exact payload in the outbox instead of sending it directly to the broker. Pigeon stores the publish intent as a Mule durable action and Mule handles retry, recovery scanning, immediate dispatch, and cleanup.

Pigeon 2.8 uses Mule Durable Actions 1.4.1 for the outbox providers, including Mule's bounded dispatch and execution queues, lane-aware runtime settings, and high-throughput durable action improvements.

This keeps scoped metadata, tracing, tenant data, and other publish interceptor output exactly as it existed at publish time. The dispatch step is intentionally separated from the original request scope.

Register the application DbContext first, then enable the Pigeon EF outbox:

builder.Services.AddDbContext<AppDbContext>(options =>
{
    options.UseSqlServer(connectionString);
});

builder.Services.AddPigeon(builder.Configuration, config =>
{
    config.UseRabbitMq();

    config.UseEntityFrameworkOutbox<AppDbContext>(outbox =>
    {
        outbox.SchemaMode = OutboxSchemaMode.AutoCreate;
        outbox.DispatchInterval = TimeSpan.FromSeconds(5);
        outbox.ImmediateDispatch = true;
        outbox.DispatchQueueCapacity = 100_000;
        outbox.ExecutionQueueCapacity = 50_000;
        outbox.CleanInterval = TimeSpan.FromMinutes(10);
        outbox.PublishedMessageRetention = TimeSpan.FromDays(1);
        outbox.DispatchBatchSize = 500;
        outbox.WorkerCount = Environment.ProcessorCount;
        outbox.MaxDegreeOfParallelism = Environment.ProcessorCount * 8;
        outbox.MaxDrainBatchesPerCycle = 8;
        outbox.MaxDrainActionsPerCycle = 10_000;
        outbox.DrainUntilEmpty = true;
        outbox.MaxRetries = 10;
    });
});

DispatchQueueCapacity controls how many durable actions can wait for Mule dispatch. ExecutionQueueCapacity controls how many actions can wait for execution after they have been locked and accepted by Mule's executor. Both can be configured globally or per outbox lane.

For the common high-throughput profile, use the outbox helper:

config.UseEntityFrameworkOutbox<AppDbContext>(outbox =>
{
    outbox.ConfigureHighThroughput();
});

Pigeon adds Mule's durable action entity to the EF model automatically, so the application DbContext does not need a DbSet or manual OnModelCreating code for Pigeon. The schema can be created with EF migrations, EnsureCreated, or your normal database deployment process.

Use OutboxSchemaMode.Manual when your database schema is created by migrations or another deployment process:

config.UseEntityFrameworkOutbox<AppDbContext>(outbox =>
{
    outbox.SchemaMode = OutboxSchemaMode.Manual;
});

When ImmediateDispatch is enabled, PublishAsync persists the outbox message as a Mule durable action and queues it for background dispatch. If an ambient TransactionScope exists, dispatch waits until the transaction commits. If the transaction rolls back, the durable action rolls back with it and nothing is dispatched.

DispatchInterval is a recovery interval, not the happy path. Mule periodically scans for pending or retryable actions and puts them back into the in-memory dispatch queue if the immediate dispatch path failed or the process restarted.

Without an ambient transaction, the message is stored and queued immediately:

await producer.PublishAsync(
    new OrderCreatedMessage { OrderId = order.Id },
    topic: "orders.created");

With an ambient transaction, the outbox write participates in that transaction and dispatch starts only after commit:

using var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled);

dbContext.Orders.Add(order);
await dbContext.SaveChangesAsync();

await producer.PublishAsync(
    new OrderCreatedMessage { OrderId = order.Id },
    topic: "orders.created");

scope.Complete();

The EF outbox uses its own DbContext instance so it does not flush pending application changes by accident. Transactional consistency with the application work is provided by the ambient transaction, so the selected database provider must support TransactionScope.

Raw messages are supported too:

await producer.PublishRawAsync(
    new ExternalAuditMessage { Id = auditId },
    topic: "external.audit");

Run the transaction sample to see the expected commit and rollback behavior without requiring a broker:

dotnet run --project samples/Pigeon.Messaging.TransactionScope.Sample/Pigeon.Messaging.TransactionScope.Sample.csproj

Inspect Outbox State

When an outbox provider is registered, Pigeon exposes IOutboxDiagnostics so an application can build health checks, dashboards, or support endpoints without querying Mule's durable action table directly:

public class OutboxHealthProbe
{
    private readonly IOutboxDiagnostics _diagnostics;

    public OutboxHealthProbe(IOutboxDiagnostics diagnostics)
    {
        _diagnostics = diagnostics;
    }

    public async Task<OutboxDiagnosticsSnapshot> GetSnapshotAsync(CancellationToken cancellationToken)
    {
        return await _diagnostics.GetSnapshotAsync(cancellationToken);
    }
}

The snapshot includes durable state and Mule runtime metrics such as pending, locked, completed, failed, throughput per minute, backlog by lane, completed per minute by lane, runtime failures, and dispatch latency averages when the selected Mule provider reports them.

Add Interceptors

Interceptors let you attach and read metadata around publishing and consuming.

public class TraceMetadata
{
    public string CorrelationId { get; set; }
}

public class TracePublishInterceptor : IPublishInterceptor
{
    public ValueTask Intercept(PublishContext context, CancellationToken cancellationToken = default)
    {
        context.AddMetadata("Trace", new TraceMetadata
        {
            CorrelationId = Guid.NewGuid().ToString("N")
        });

        return ValueTask.CompletedTask;
    }
}

public class TraceConsumeInterceptor : IConsumeInterceptor
{
    public ValueTask Intercept(ConsumeContext context, CancellationToken cancellationToken = default)
    {
        var trace = context.GetMetadata<TraceMetadata>("Trace");
        return ValueTask.CompletedTask;
    }
}

Register interceptors after calling AddPigeon:

builder.Services
    .AddPigeon(builder.Configuration, config =>
    {
        config.UseRabbitMq();
    })
    .AddConsumeInterceptor<TraceConsumeInterceptor>()
    .AddPublishInterceptor<TracePublishInterceptor>();

Sample appsettings.json

{
  "Pigeon": {
    "Domain": "YourApp.Domain",
    "ConsumerExecution": {
      "AcknowledgementMode": "OnHandlerSuccess",
      "MaxConcurrency": 256,
      "QueueCapacity": 10000,
      "PrefetchCount": 256,
      "HandlerTimeout": "00:02:00"
    },
    "Outbox": {
      "Enabled": true,
      "ImmediateDispatch": true,
      "DispatchQueueCapacity": 100000,
      "ExecutionQueueCapacity": 50000,
      "DispatchBatchSize": 500,
      "WorkerCount": 16,
      "MaxDegreeOfParallelism": 128,
      "MaxDrainBatchesPerCycle": 8,
      "MaxDrainActionsPerCycle": 10000,
      "DrainUntilEmpty": true,
      "DispatchInterval": "00:00:05"
    },
    "MessageBrokers": {
      "RabbitMq": {
        "Url": "amqp://guest:guest@localhost:5672",
        "PublisherChannelPoolSize": 16
      },
      "Kafka": {
        "BootstrapServers": "localhost:9092",
        "UserName": "test",
        "Password": "test",
        "SecurityProtocol": "PlainText",
        "SaslMechanism": "Plain",
        "Acks": "All"
      },
      "AzureServiceBus": {
        "ConnectionString": "Endpoint=sb://test/;SharedAccessKeyName=Root;SharedAccessKey=abc"
      },
      "AzureEventGrid": {
        "ServiceBusEndpoint": "",
        "Endpoints": {
          "Greeting": {
            "Url": "https://example.eventgrid.azure.net/api/events",
            "AccessKey": "event-grid-access-key"
          },
          "Users": {
            "Url": "https://example-users.eventgrid.azure.net/api/events",
            "AccessKey": "event-grid-access-key"
          }
        },
        "TopicRouting": {
          "commands.demo.hello-world": "Greeting",
          "events.demo.user-created": "Users"
        }
      },
      "AzureEventHub": {
        "ConnectionString": "Endpoint=sb://tests/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=abc"
      }
    }
  }
}

Extensible by Design

  • Pluggable broker adapters.
  • Automatic consumer scanning by ConsumerAttribute.
  • Built-in support for message versioning and interceptors.
  • Clean separation of concerns through ConsumingManager, ProducingManager, adapters, and interceptors.

Upcoming Features

  • Enhanced Management Capabilities Add health checking, multi-publishing, multi-consuming, failover and more.
  • Support for Amazon SQS and Mosquitto Add adapters for Amazon SQS and Mosquitto message brokers.
Product Compatible and additional computed target framework versions.
.NET 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 is compatible.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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
2.8.0 73 8/18/2026
2.7.0 81 8/18/2026
2.6.0 91 8/17/2026
2.5.0 91 8/16/2026
2.4.0 97 8/14/2026
2.3.0 91 8/10/2026
2.2.0 91 8/7/2026
2.1.0 88 8/6/2026
2.0.0 101 7/23/2026
1.1.7 167 1/5/2026
1.1.6 137 1/5/2026
1.1.5 257 12/4/2025
1.1.4 254 12/3/2025
1.1.3 733 12/2/2025
1.1.2 716 12/2/2025
1.1.1 712 12/2/2025
1.1.0 716 12/2/2025