Zaiets.EventBus.Lite
1.0.0
dotnet add package Zaiets.EventBus.Lite --version 1.0.0
NuGet\Install-Package Zaiets.EventBus.Lite -Version 1.0.0
<PackageReference Include="Zaiets.EventBus.Lite" Version="1.0.0" />
<PackageVersion Include="Zaiets.EventBus.Lite" Version="1.0.0" />
<PackageReference Include="Zaiets.EventBus.Lite" />
paket add Zaiets.EventBus.Lite --version 1.0.0
#r "nuget: Zaiets.EventBus.Lite, 1.0.0"
#:package Zaiets.EventBus.Lite@1.0.0
#addin nuget:?package=Zaiets.EventBus.Lite&version=1.0.0
#tool nuget:?package=Zaiets.EventBus.Lite&version=1.0.0
Zaiets.EventBus.Lite
In-process event bus for .NET 10 — lightweight pub/sub, async handlers, configurable retry, dead letter queue, and a composable middleware pipeline. Zero external dependencies beyond Microsoft.Extensions.*.
Installation
dotnet add package Zaiets.EventBus.Lite
Quick Start
1. Define an event
using Zaiets.EventBus.Lite.Abstractions;
public sealed record OrderPlaced(Guid OrderId, decimal Total) : EventBase;
2. Write a handler
using Zaiets.EventBus.Lite.Abstractions;
public sealed class SendConfirmationEmailHandler : IEventHandler<OrderPlaced>
{
private readonly IEmailService _email;
public SendConfirmationEmailHandler(IEmailService email) => _email = email;
public async Task HandleAsync(OrderPlaced @event, CancellationToken ct)
{
await _email.SendAsync(@event.OrderId, ct);
}
}
3. Register with DI
// Program.cs
builder.Services
.AddEventBus(builder.Configuration)
.AddHandler<OrderPlaced, SendConfirmationEmailHandler>()
.AddMiddleware<LoggingMiddleware>()
.AddMiddleware<ActivityMiddleware>();
4. Publish
public class OrderService
{
private readonly IEventBus _bus;
public OrderService(IEventBus bus) => _bus = bus;
public async Task PlaceOrderAsync(Order order, CancellationToken ct)
{
// ... business logic ...
await _bus.PublishAsync(new OrderPlaced(order.Id, order.Total), ct);
}
}
Configuration
Bind from appsettings.json:
{
"EventBus": {
"ContinueOnHandlerError": true,
"MaxRetryAttempts": 2,
"RetryDelay": "00:00:00.200",
"ConcurrentHandlers": false,
"MaxConcurrency": 0,
"HandlerTimeout": "00:00:30",
"EnableLogging": true
}
}
Or configure inline:
builder.Services
.AddEventBus(configure: opts =>
{
opts.MaxRetryAttempts = 3;
opts.ConcurrentHandlers = true;
opts.MaxConcurrency = 4;
opts.HandlerTimeout = TimeSpan.FromSeconds(10);
});
| Option | Default | Description |
|---|---|---|
ContinueOnHandlerError |
true |
Keep dispatching to remaining handlers when one fails |
MaxRetryAttempts |
2 |
Retries before routing to the dead letter queue |
RetryDelay |
200 ms |
Base delay; multiplied by attempt index (linear back-off) |
ConcurrentHandlers |
false |
Run all handlers for an event concurrently |
MaxConcurrency |
0 (∞) |
Throttle concurrent handlers via a semaphore |
HandlerTimeout |
30 s |
Per-handler timeout; Timeout.InfiniteTimeSpan to disable |
EnableLogging |
true |
Structured ILogger output for publish/dispatch cycles |
Assembly Scanning
Instead of registering handlers one by one, scan an assembly:
builder.Services
.AddEventBus()
.AddHandlersFromAssembly(typeof(OrderPlaced).Assembly);
Inline Subscriptions
For lightweight wiring without a dedicated class:
// Persistent subscription — dispose token to unsubscribe
using var token = bus.Subscribe<OrderPlaced>(async (evt, ct) =>
{
Console.WriteLine($"Order {evt.OrderId} placed");
await Task.CompletedTask;
});
// One-time subscription — automatically removed after first fire
bus.SubscribeOnce<OrderPlaced>(async (evt, ct) =>
{
Console.WriteLine("First order ever!");
await Task.CompletedTask;
});
Middleware Pipeline
Middlewares wrap every publish call. They execute in registration order (first added = outermost).
Built-in middlewares
| Middleware | Purpose |
|---|---|
LoggingMiddleware |
ILogger-based structured logging with elapsed time |
ActivityMiddleware |
OpenTelemetry Activity spans via System.Diagnostics |
ValidationMiddleware |
DataAnnotations validation; throws EventValidationException on failure |
Custom middleware
using Zaiets.EventBus.Lite.Abstractions;
public sealed class CorrelationMiddleware : IEventMiddleware
{
public async Task InvokeAsync(IEvent @event, EventMiddlewareDelegate next, CancellationToken ct)
{
using (LogContext.PushProperty("CorrelationId", @event.EventId))
{
await next(@event, ct);
}
}
}
Register it:
.AddMiddleware<CorrelationMiddleware>()
Dead Letter Queue
Failed events (after all retries) are routed to IDeadLetterQueue.
Reading the DLQ
public class DeadLetterMonitor
{
private readonly IDeadLetterQueue _dlq;
public DeadLetterMonitor(IDeadLetterQueue dlq) => _dlq = dlq;
public async Task PrintAsync()
{
var letters = await _dlq.GetAllAsync();
foreach (var dl in letters)
Console.WriteLine($"[{dl.FailedAt}] {dl.Event.GetType().Name} — {dl.Exception.Message}");
}
}
Custom DLQ (e.g., write to a database)
public class SqlDeadLetterQueue : IDeadLetterQueue
{
// ... your EF Core / Dapper implementation ...
}
// Registration — call UseDeadLetterQueue BEFORE AddHandler calls
builder.Services
.AddEventBus()
.UseDeadLetterQueue<SqlDeadLetterQueue>();
OpenTelemetry Integration
Wire up ActivityMiddleware and listen on the named source:
using OpenTelemetry.Trace;
builder.Services.AddOpenTelemetry().WithTracing(tracing =>
{
tracing
.AddSource(Zaiets.EventBus.Lite.Middleware.ActivityMiddleware.Source.Name)
.AddJaegerExporter();
});
Publish Multiple Events
var events = orders.Select(o => new OrderPlaced(o.Id, o.Total));
await bus.PublishManyAsync(events, cancellationToken);
DataAnnotations Validation
Add ValidationMiddleware and decorate your events:
using System.ComponentModel.DataAnnotations;
public sealed record OrderPlaced(
[Required] Guid OrderId,
[Range(0.01, double.MaxValue)] decimal Total) : EventBase;
Publishing an invalid event throws EventValidationException with a list of ValidationResult errors before any handler runs.
API Reference
IEventBus
| Member | Description |
|---|---|
PublishAsync<TEvent>(event, ct) |
Publish a single event through the pipeline |
PublishManyAsync<TEvent>(events, ct) |
Publish a sequence of events |
Subscribe<TEvent>(handler) |
Inline persistent subscription; returns IDisposable |
SubscribeOnce<TEvent>(handler) |
Inline one-time subscription; returns IDisposable |
IEventHandler<TEvent>
Implement and register to handle TEvent via DI.
IEventMiddleware
Implement InvokeAsync(event, next, ct) to create cross-cutting pipeline behaviour.
IDeadLetterQueue
| Member | Description |
|---|---|
EnqueueAsync(deadLetter, ct) |
Add a failed event |
GetAllAsync(ct) |
Retrieve all stored failures |
PurgeAsync(ct) |
Clear the queue |
Count |
Current queue depth |
License
MIT © 2025 Vladyslav Zaiets
| Product | Versions 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. |
-
net10.0
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Options (>= 10.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.0)
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.0.0 | 125 | 5/3/2026 |