Syed.BuildingBlocks 1.2.1

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

Syed.Messaging

A transport-agnostic .NET messaging framework with built-in retry, DLQ, middleware, outbox, inbox, sagas, and observability.

Build & Test License .NET Version


Why Syed.Messaging?

Most .NET messaging libraries force you into a specific broker. Syed.Messaging gives you one API across RabbitMQ, Kafka, and Azure Service Bus — with production patterns built in, not bolted on.

// Register everything in one fluent chain
services.AddMessaging(builder =>
{
    builder.UseRabbitMq(o => o.ConnectionString = "amqp://localhost");
    builder.AddMiddleware<TenantContextMiddleware>();
    builder.AddConsumer<OrderCreated, OrderCreatedHandler>(o =>
    {
        o.Destination = "orders.created";
        o.SubscriptionName = "orders.consumer";
        o.RetryPolicy = new RetryPolicy { MaxRetries = 3, Backoff = RetryBackoff.Exponential };
        o.MaxConcurrency = 4;
    });
});

✨ Features

Feature Description
Transport-agnostic RabbitMQ, Kafka, Azure Service Bus — same API
Typed consumers IMessageHandler<T> with automatic deserialization
Per-destination queues Each consumer gets its own queue — no cross-talk (v1.2.0)
Middleware pipeline IMessageMiddleware for cross-cutting concerns (v1.1.0)
Retry + DLQ Configurable retry policies with exponential backoff and dead-letter routing
Outbox pattern EF Core-based transactional outbox with raw mode support
Inbox deduplication Idempotent consumer pattern via EF Core
Saga orchestration State management, correlation, timeouts, distributed locking
RPC support Request/response messaging with IRpcHandler<TReq, TRes>
Observability OpenTelemetry spans + 7 Prometheus-ready metrics
Health checks ASP.NET Core health check integration per transport
SignalR bridge Route messaging events to SignalR hubs
Service discovery Kubernetes DNS, Consul, standard DNS
Message versioning VersionedMessage<T>, schema registry, compatibility rules

📦 Packages

Package Description
Syed.Messaging.Abstractions Core interfaces: IMessageBus, IMessageHandler<T>, IMessageMiddleware, IMessageTransport
Syed.Messaging.Core GenericMessageConsumer<T>, RpcMessageConsumer, MessagingBuilder, retry/DLQ logic
Syed.Messaging.RabbitMq RabbitMQ transport with topology builder, publisher confirms, per-destination queues
Syed.Messaging.Kafka Kafka transport with topic-based retry/DLQ
Syed.Messaging.AzureServiceBus Azure Service Bus transport with scheduled message retry
Syed.Messaging.Outbox.EfCore Transactional outbox with OutboxPublisherService and raw mode
Syed.Messaging.Inbox.EfCore Idempotent consumer inbox pattern
Syed.Messaging.Sagas Saga primitives: state, correlation, timeouts, locking
Syed.Messaging.Sagas.EfCore EF Core persistence for saga state and timeouts
Syed.Messaging.Sagas.Redis Redis distributed saga locking
Syed.Messaging.OpenTelemetry Activity spans for publish/consume and trace context propagation
Syed.Messaging.HealthChecks ASP.NET Core health check integration
Syed.Messaging.SignalR Bridge messaging events to SignalR hubs
Syed.Messaging.Aspire .NET Aspire integration helpers
Syed.BuildingBlocks Shared utilities and feature flags

Installation

Packages are published to GitHub Packages:

dotnet add package Syed.Messaging.Core --version 1.2.0
dotnet add package Syed.Messaging.RabbitMq --version 1.2.0

🚀 Quick Start

1. Define a message and handler

[MessageType("orders.created")]
public record OrderCreated(Guid OrderId, string CustomerId);

public class OrderCreatedHandler : IMessageHandler<OrderCreated>
{
    public async Task HandleAsync(OrderCreated message, MessageContext ctx, CancellationToken ct)
    {
        Console.WriteLine($"Processing order {message.OrderId} for {message.CustomerId}");
    }
}

2. Wire up in Program.cs

var builder = Host.CreateDefaultBuilder(args);
builder.ConfigureServices(services =>
{
    services.AddMessaging(m =>
    {
        m.UseRabbitMq(o =>
        {
            o.ConnectionString = "amqp://guest:guest@localhost:5672";
            o.MainExchangeName = "myapp.events";
        });

        m.AddConsumer<OrderCreated, OrderCreatedHandler>(o =>
        {
            o.Destination = "orders.created";
            o.SubscriptionName = "orders.consumer";
        });
    });
});

await builder.Build().RunAsync();

3. Publish messages

var bus = serviceProvider.GetRequiredService<IMessageBus>();
await bus.PublishAsync("orders.created", new OrderCreated(Guid.NewGuid(), "cust-123"));

🔌 Middleware Pipeline

Middleware runs before every handler — ideal for tenant context, logging, auth propagation:

public class TenantContextMiddleware : IMessageMiddleware
{
    public async Task InvokeAsync(IMessageEnvelope envelope, IServiceProvider sp, Func<Task> next)
    {
        if (envelope.Headers.TryGetValue("tenant-id", out var tenantId))
        {
            var ctx = sp.GetRequiredService<ITenantContext>();
            ctx.SetTenant(tenantId);
        }
        await next(); // handler runs with tenant context set
    }
}

// Register
services.AddMessaging(m => m.AddMiddleware<TenantContextMiddleware>());

Middlewares execute in registration order (first registered = outermost wrapper).


🐇 RabbitMQ Transport

Per-destination queue routing (v1.2.0):

Publisher ──routing key──► Main Exchange (Direct)
                              │
                    ┌─────────┼──────────┐
                    ▼         ▼          ▼
            orders.queue  billing.queue  notifications.queue
                    │
                    ▼ (on failure)
              Retry Exchange ──► Retry Queue (TTL) ──DLX──► Main Exchange
                                                            (preserves routing key)
  • Each AddConsumer<T>() auto-declares its own queue bound by destination
  • Retry queue preserves original routing key on DLX — messages return to the correct queue
  • DLQ captures poison messages with diagnostic headers (x-poison-*)

🧵 Kafka Partition Strategy

Kafka message ordering is partition-scoped, not topic-scoped. To preserve ordering for a business entity, publish a stable partition-key.

await bus.PublishRawAsync(
    "orders.created",
    JsonSerializer.SerializeToUtf8Bytes(new OrderCreated(orderId, customerId)),
    "OrderCreated",
    new Dictionary<string, string> { ["partition-key"] = customerId });

Practical guidance

  • Use aggregate IDs (CustomerId, OrderId, TenantId) as partition-key.
  • Same key means same partition, which gives deterministic in-order handling for that key.
  • Different keys can run in parallel with:
services.AddMessaging(m =>
{
    m.UseKafka(k =>
    {
        k.Consumer.MaxConcurrentPartitions = 4;
        k.Consumer.PartitionAssignmentStrategy = KafkaPartitionAssignmentStrategy.CooperativeSticky;
    });
});

This gives you the common production shape: strict ordering per entity, concurrency across entities.


📤 Outbox Pattern

Guarantee at-least-once delivery with transactional outbox:

// Save to DB + queue atomically
var outbox = scope.ServiceProvider.GetRequiredService<IOutboxStore>();
await outbox.SaveAsync(new OutboxMessage
{
    Destination = "orders.created",
    MessageType = "OrderCreated",
    Payload = JsonSerializer.SerializeToUtf8Bytes(order)
});
await dbContext.SaveChangesAsync(); // single transaction

// OutboxPublisherService polls and publishes in background
services.AddHostedService<OutboxPublisherService>();

Supports raw mode for anonymous payloads (no CLR type resolution needed).


♻ Saga Orchestration

Long-running workflows with state management and distributed locking:

public class OrderSaga : ISagaHandler<OrderSagaState, OrderCreated>
{
    public async Task HandleAsync(OrderSagaState state, OrderCreated msg, ISagaContext ctx)
    {
        state.OrderId = msg.OrderId;
        await ctx.SendAsync("inventory.reserve", new ReserveInventory(msg.OrderId));
        ctx.SetTimeout(TimeSpan.FromMinutes(5), "InventoryTimeout");
    }
}

Persistence: EF Core or in-memory. Locking: Redis, in-memory, or no-op.


📊 Observability

// OpenTelemetry integration
services.AddOpenTelemetry()
    .WithTracing(b => b.AddSource("Syed.Messaging"));

// Built-in metrics (System.Diagnostics.Metrics)
// - messages_published, messages_consumed, messages_retried
// - messages_dead_lettered, handler_duration, handler_errors
// - active_consumers

🧪 Testing

76 tests across 5 test projects:

dotnet test --configuration Release

📋 Changelog

v1.2.0 — Per-Destination Queue Routing

  • SubscribeAsync auto-declares per-destination queues bound by routing key
  • Each consumer only receives its own messages — fixes shared-queue poison issue
  • Retry/DLQ routing preserves original destination
  • MainQueueName deprecated

v1.1.0 — Middleware Pipeline

  • IMessageMiddleware interface for pre-handler cross-cutting concerns
  • Integrated into GenericMessageConsumer and RpcMessageConsumer
  • MessagingBuilder.AddMiddleware<T>() for fluent registration

🤝 Contributing

PRs, issues, and design discussions are welcome. This is a platform-style library — architectural feedback is especially valuable.

See ROADMAP.md for planned features and current status.


📝 License

MIT

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

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 129 6/1/2026
1.2.2 114 5/24/2026
1.2.1 109 5/11/2026