README.md

February 17, 2026 · View on GitHub

LiteBus Logo
LiteBus

A lightweight, high-performance mediator for building clean, scalable, and testable .NET applications with CQS and DDD.

Build Status Code Coverage NuGet Version Wiki

LiteBus is a modern, powerful, and perpetually free alternative to MediatR.

It is, and always will be, governed by the MIT license. LiteBus helps you implement Command Query Separation (CQS) and Domain-Driven Design (DDD) patterns by providing a clean, decoupled architecture for your application's business logic.


Why LiteBus?

  • Truly Semantic: Go beyond generic requests. With first-class contracts like ICommand<TResult>, IQuery<TResult>, and IEvent, your code becomes self-documenting. You can even publish clean POCOs as domain events.
  • High Performance: Designed for minimal overhead. Handler metadata is cached on startup, and dependencies are resolved lazily. Large datasets are handled efficiently with IAsyncEnumerable<T> streaming.
  • Granular Pipeline Control: Go beyond simple "behaviors". LiteBus provides a full pipeline with distinct, type-safe Pre-Handlers, Post-Handlers, and Error-Handlers for each message.
  • Open Generic Handlers: Write a single pre/post/error handler once and have it automatically apply to every message type matching its constraints — perfect for cross-cutting concerns like logging, validation, and metrics.
  • Advanced Event Concurrency: Take full control of event processing. Configure Sequential or Parallel execution for both priority groups and for handlers within the same group to fine-tune throughput.
  • Resilient & Durable: Guarantee at-least-once execution for critical commands with a built-in durable Command Inbox.
  • DI-Agnostic by Design: Decoupled from any specific DI container. First-class integration for Microsoft DI and Autofac is provided, with a simple adapter pattern to support others.

Quick Start

1. Install Packages

Install the modules you need. The core messaging infrastructure is included automatically.

# For Commands
dotnet add package LiteBus.Commands.Extensions.Microsoft.DependencyInjection

# For Queries
dotnet add package LiteBus.Queries.Extensions.Microsoft.DependencyInjection

# For Events
dotnet add package LiteBus.Events.Extensions.Microsoft.DependencyInjection

2. Define Your Messages and Handlers

Command: Create a Product

// The Command
public sealed record CreateProductCommand(string Name, decimal Price) : ICommand<Guid>;

// The Handler
public sealed class CreateProductCommandHandler : ICommandHandler<CreateProductCommand, Guid>
{
    public Task<Guid> HandleAsync(CreateProductCommand command, CancellationToken cancellationToken)
    {
        var productId = Guid.NewGuid(); // Your business logic here...
        Console.WriteLine($"Product '{command.Name}' created with ID: {productId}");
        return Task.FromResult(productId);
    }
}

Query: Get a Product by ID

// The Query
public sealed record GetProductByIdQuery(Guid Id) : IQuery<ProductDto>;

// The DTO
public sealed record ProductDto(Guid Id, string Name, decimal Price);

// The Handler
public sealed class GetProductByIdQueryHandler : IQueryHandler<GetProductByIdQuery, ProductDto>
{
    public Task<ProductDto> HandleAsync(GetProductByIdQuery query, CancellationToken cancellationToken)
    {
        // Your data retrieval logic here...
        var product = new ProductDto(query.Id, "Sample Product", 99.99m);
        return Task.FromResult(product);
    }
}

Event: A Product was Created

// The Event (can be a simple POCO)
public sealed record ProductCreatedEvent(Guid ProductId, string Name);

// The Handler
public sealed class ProductCreatedEventHandler : IEventHandler<ProductCreatedEvent>
{
    public Task HandleAsync(ProductCreatedEvent @event, CancellationToken cancellationToken)
    {
        // Your side-effect logic here (e.g., send an email, update a projection)
        Console.WriteLine($"Handling side effects for new product '{@event.Name}'...");
        return Task.CompletedTask;
    }
}

3. Configure and Mediate

Register LiteBus and its modules in Program.cs, then inject the mediators into your services or controllers.

// In Program.cs
builder.Services.AddLiteBus(liteBus =>
{
    var appAssembly = typeof(Program).Assembly;

    // Scan the assembly for all command/query/event handlers
    liteBus.AddCommandModule(module => module.RegisterFromAssembly(appAssembly));
    liteBus.AddQueryModule(module => module.RegisterFromAssembly(appAssembly));
    liteBus.AddEventModule(module => module.RegisterFromAssembly(appAssembly));
});

// In your API Controller or Service
public class ProductController : ControllerBase
{
    private readonly ICommandMediator _commandMediator;
    private readonly IQueryMediator _queryMediator;
    private readonly IEventMediator _eventMediator;

    public ProductController(ICommandMediator cmd, IQueryMediator qry, IEventMediator evt)
    {
        _commandMediator = cmd;
        _queryMediator = qry;
        _eventMediator = evt;
    }

    [HttpPost]
    public async Task<IActionResult> Create(CreateProductCommand command)
    {
        // 1. Send a command to create the product
        var productId = await _commandMediator.SendAsync(command);

        // 2. Publish an event to handle side effects
        await _eventMediator.PublishAsync(new ProductCreatedEvent(productId, command.Name));

        // 3. Query for the newly created product to return it
        var productDto = await _queryMediator.QueryAsync(new GetProductByIdQuery(productId));

        return Ok(productDto);
    }
}

Key Features

A Semantic & Granular Pipeline

LiteBus provides a rich set of interfaces that make your pipeline explicit and powerful. Each message type (Command, Query, Event) has its own set of Pre-Handlers, Post-Handlers, and Error-Handlers.

This allows for fine-grained control, such as running validation logic, enriching a message, or logging results at specific stages of the pipeline. You can also share data between handlers via the AmbientExecutionContext.

// A semantic validator that runs before the main handler

public sealed class PlaceOrderValidator : ICommandValidator<PlaceOrderCommand> // or ICommandPreHandler<PlaceOrderCommand>
{
    public Task ValidateAsync(PlaceOrderCommand command, CancellationToken cancellationToken)
    {
        if (command.LineItems.Count == 0)
        {
            throw new ValidationException("At least one line item is required.");
        }
        return Task.CompletedTask;
    }
}

// A post-handler that runs after the command is successfully handled
public sealed class PlaceOrderNotifier : ICommandPostHandler<PlaceOrderCommand, Guid>
{
    public Task PostHandleAsync(PlaceOrderCommand command, Guid orderId, CancellationToken cancellationToken)
    {
        // Publish an OrderPlacedEvent with the result from the command handler
        return _eventPublisher.PublishAsync(new OrderPlacedEvent(orderId));
    }
}

The mediator also supports polymorphic dispatch, allowing handlers for a base message type to process any derived messages.

Open Generic Handlers for Cross-Cutting Concerns

Write a single handler that automatically applies to every command, query, or event. No changes to existing messages required.

// This pre-handler runs before EVERY command — registered once, applied everywhere
public sealed class CommandLogger<T> : ICommandPreHandler<T> where T : ICommand
{
    public Task PreHandleAsync(T message, CancellationToken cancellationToken)
    {
        Console.WriteLine($"Executing: {typeof(T).Name}");
        return Task.CompletedTask;
    }
}

// RegisterFromAssembly automatically discovers open generic handlers in the assembly
builder.Services.AddLiteBus(liteBus =>
{
    liteBus.AddCommandModule(module =>
    {
        module.RegisterFromAssembly(typeof(Program).Assembly); // picks up CommandLogger<> too
    });
});

// Or register explicitly if the handler is in a different assembly
builder.Services.AddLiteBus(liteBus =>
{
    liteBus.AddCommandModule(module =>
    {
        module.Register(typeof(CommandLogger<>));  // from an external library
        module.RegisterFromAssembly(typeof(Program).Assembly);
    });
});

RegisterFromAssembly automatically discovers open generic handlers in the scanned assembly — no separate Register(typeof(...)) call is needed. Use explicit registration only when the handler lives in a different assembly. LiteBus closes the generic at startup for each concrete message type. Generic constraints (where T : ICommand, class, struct, new()) are fully respected. Registration order does not matter.

Advanced Eventing with Concurrency Control

Define execution priority and concurrency for event handlers to manage complex workflows.

// This handler runs first
[HandlerPriority(1)]
public class ValidateOrderHandler : IEventHandler<OrderPlacedEvent> { /* ... */ }

// These two handlers run concurrently after the validation handler completes
[HandlerPriority(2)]
public class PersistOrderHandler : IEventHandler<OrderPlacedEvent> { /* ... */ }

[HandlerPriority(2)]
public class NotifyInventoryHandler : IEventHandler<OrderPlacedEvent> { /* ... */ }

// Configure execution strategy at runtime
await _eventMediator.PublishAsync(e, new EventMediationSettings
{
    Execution = new EventMediationExecutionSettings
    {
        // Run priority groups one after another
        PriorityGroupsConcurrencyMode = ConcurrencyMode.Sequential,
        // Run handlers within the same group in parallel
        HandlersWithinSamePriorityConcurrencyMode = ConcurrencyMode.Parallel
    }
});

Contextual Filtering with Tags

Execute specific handlers based on runtime context, such as the request origin.

// This handler only runs if the "api" tag is specified
[HandlerTag("api")]
public class ApiValidationPreHandler : ICommandPreHandler<CreateProductCommand> { /* ... */ }

// Mediate with a tag
await _commandMediator.SendAsync(command, new CommandMediationSettings
{
    Filters = { Tags = ["api"] }
});

Durable Command Inbox for Guaranteed Execution

Ensure critical commands are never lost by marking them for durable storage and deferred processing.

// This command will be stored in a durable inbox and processed by a background service
[StoreInInbox]
public sealed record ProcessPaymentCommand(Guid OrderId, decimal Amount) : ICommand;

Modular by Design

LiteBus is built on a modular, DI-agnostic runtime. You only install what you need.

CategoryPackage NameNuGet
MetapackageLiteBusNuGet
Core ModulesLiteBus.CommandsNuGet
LiteBus.QueriesNuGet
LiteBus.EventsNuGet
LiteBus.MessagingNuGet
LiteBus.RuntimeNuGet
AbstractionsLiteBus.Commands.AbstractionsNuGet
LiteBus.Queries.AbstractionsNuGet
LiteBus.Events.AbstractionsNuGet
LiteBus.Messaging.AbstractionsNuGet
LiteBus.Runtime.AbstractionsNuGet
MS.DI ExtensionsLiteBus.Extensions.Microsoft.DependencyInjectionNuGet
LiteBus.Commands.Extensions.Microsoft.DependencyInjectionNuGet
LiteBus.Queries.Extensions.Microsoft.DependencyInjectionNuGet
LiteBus.Events.Extensions.Microsoft.DependencyInjectionNuGet
LiteBus.Messaging.Extensions.Microsoft.DependencyInjectionNuGet
LiteBus.Runtime.Extensions.Microsoft.DependencyInjectionNuGet
Autofac ExtensionsLiteBus.Commands.Extensions.AutofacNuGet
LiteBus.Queries.Extensions.AutofacNuGet
LiteBus.Events.Extensions.AutofacNuGet
LiteBus.Messaging.Extensions.AutofacNuGet
LiteBus.Runtime.Extensions.AutofacNuGet
HostingLiteBus.Commands.Extensions.Microsoft.HostingNuGet

Migrating from MediatR

LiteBus offers a more semantic and feature-rich alternative to MediatR. If you're migrating, here’s how the core concepts map.

  • Requests (IRequest<TResponse> and IRequest)
    In MediatR, IRequest is used for both commands and queries. LiteBus separates these for CQS clarity:

    • Use ICommand<TResult> for operations that change state and return a value.
    • Use IQuery<TResult> for read-only operations.
    • Use ICommand for fire-and-forget operations that don't return a value.
  • Notifications (INotification)
    MediatR's INotification is equivalent to LiteBus's IEvent. A key advantage of LiteBus is that you don't need to implement any interface. You can publish any Plain Old C# Object (POCO) as an event, keeping your domain model completely clean.

  • Stream Requests (IStreamRequest<TResponse>)
    This maps directly to IStreamQuery<TResult> in LiteBus, which returns an IAsyncEnumerable<TResult>. LiteBus semantically treats streams as a query concern.

  • Pipeline Behaviors (IPipelineBehavior<,>)
    MediatR uses a generic IPipelineBehavior for cross-cutting concerns. LiteBus provides a more granular and type-safe pipeline with distinct stages for each message type:

    • ICommandPreHandler<TCommand> / IQueryPreHandler<TQuery>: Run before the main handler. Ideal for validation (ICommandValidator is a semantic shortcut for this).
    • ICommandPostHandler<TCommand, TResult> / IQueryPostHandler<TQuery, TResult>: Run after the main handler, with access to the result.
    • ICommandErrorHandler<TCommand> / IQueryErrorHandler<TQuery>: Centralized error handling for specific message types.
    • Open generic handlers (e.g., MyHandler<T> : ICommandPreHandler<T> where T : ICommand) work similarly to MediatR's open generic IPipelineBehavior<,> — register once and they apply to all matching message types automatically.

This granular approach eliminates the need for generic pipeline behaviors and provides a more expressive and maintainable way to build your processing pipeline.

Our Commitment to Open Source

LiteBus was created to provide the .NET community with a modern, high-performance, and truly free open-source tool. We believe essential infrastructure libraries should be community-driven and accessible to everyone without financial barriers.

LiteBus will always be free and licensed under the MIT license.

We are committed to maintaining and evolving LiteBus as a community project. Contributions are welcome, and we encourage you to get involved.

Documentation

For detailed documentation, feature guides, and examples, please visit the LiteBus Wiki.