Goodtocode.InboxOutbox
1.1.9
See the version list below for details.
dotnet add package Goodtocode.InboxOutbox --version 1.1.9
NuGet\Install-Package Goodtocode.InboxOutbox -Version 1.1.9
<PackageReference Include="Goodtocode.InboxOutbox" Version="1.1.9" />
<PackageVersion Include="Goodtocode.InboxOutbox" Version="1.1.9" />
<PackageReference Include="Goodtocode.InboxOutbox" />
paket add Goodtocode.InboxOutbox --version 1.1.9
#r "nuget: Goodtocode.InboxOutbox, 1.1.9"
#:package Goodtocode.InboxOutbox@1.1.9
#addin nuget:?package=Goodtocode.InboxOutbox&version=1.1.9
#tool nuget:?package=Goodtocode.InboxOutbox&version=1.1.9
Goodtocode.InboxOutbox
Infrastructure library for implementing the Inbox/Outbox pattern for event-driven architecture with .NET
Goodtocode.InboxOutbox provides a complete infrastructure solution for implementing the Inbox/Outbox messaging pattern to support Event-Driven Architecture (EDA). The library integrates seamlessly with Azure Service Bus, Event Grid, and Event Hub, with primary focus on Service Bus scenarios. It ensures reliable event publishing and processing through transactional outbox and idempotent inbox patterns.
Features
- Transactional outbox pattern for guaranteed event publishing
- Idempotent inbox pattern for duplicate detection
- EF Core interceptor for automatic outbox message capture
- Background workers for outbox dispatch and inbox processing
- Event type registry for serialization/deserialization
- Per-bounded-context DbContext support
- Compatible with Azure Service Bus, Event Grid, and Event Hub
- Built for eventual consistency and cross-context communication
- Lightweight, extensible, and follows DDD principles
Quick-Start Steps
- Clone this repository
git clone https://github.com/goodtocode/aspect-inboxoutbox.git - Install .NET SDK (latest recommended)
winget install Microsoft.DotNet.SDK --silent - Build the solution
cd src dotnet build Goodtocode.InboxOutbox.sln - Run tests
cd Goodtocode.InboxOutbox.Tests dotnet test
Install Prerequisites
- .NET SDK (latest)
- Visual Studio (latest) or VS Code
Installation & Usage
1. Add the NuGet Package
dotnet add package Goodtocode.InboxOutbox
2. Add EF Configuration
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyInboxOutbox(); // from SDK
base.OnModelCreating(modelBuilder);
}
3. Add Services in Program.cs
services.AddInboxOutbox(configuration);
4. Add Migrations
dotnet ef migrations add AddInboxOutbox
dotnet ef database update
Expected DbContext Structure (per bounded context)
AccountingDbContext
- InboxMessage
- OutboxMessage
- Projection tables
IdentityDbContext
- InboxMessage
- OutboxMessage
- Projection tables
BillingDbContext
- InboxMessage
- OutboxMessage
- Projection tables
SDK Components
1. Entities
OutboxMessage- Stores unpublished domain eventsInboxMessage- Stores received events for idempotent processing
2. EF Configurations
OutboxMessageConfiguration- Entity configuration for outbox tableInboxMessageConfiguration- Entity configuration for inbox table
3. Interceptors
OutboxSaveChangesInterceptor- Automatically captures domain events and writes to outbox
4. Hosted Services
OutboxDispatcherHostedService- Background worker that publishes outbox messagesInboxProcessorHostedService- Background worker that processes inbox messages
5. Event Serialization Helpers
IEventTypeRegistry- Interface for event type resolutionDefaultEventTypeRegistry- Default implementation for type mapping
6. Interfaces
IEventBus- Event bus abstractionIEventPublisher- Event publishing interfaceIEventConsumer- Event consumption interface
7. Extension Methods
modelBuilder.ApplyInboxOutbox()- Applies inbox/outbox EF configurationsservices.AddInboxOutbox()- Registers all required services
Top Use Case Examples
1. Outbox SaveChanges Interceptor
The interceptor automatically captures domain events during SaveChangesAsync and writes them to the outbox table:
public sealed class OutboxSaveChangesInterceptor : SaveChangesInterceptor
{
public override ValueTask<InterceptionResult<int>> SavingChangesAsync(
DbContextEventData eventData,
InterceptionResult<int> result,
CancellationToken cancellationToken = default)
{
var context = (DbContext)eventData.Context!;
var domainEntities = context.ChangeTracker
.Entries<DomainEntity>()
.Where(e => e.Entity.DomainEvents.Any())
.ToList();
foreach (var entry in domainEntities)
{
foreach (var domainEvent in entry.Entity.DomainEvents)
{
var outbox = new OutboxMessage
{
Id = Guid.NewGuid(),
OccurredOnUtc = domainEvent.OccurredOnUtc,
Type = domainEvent.GetType().Name,
Payload = JsonSerializer.Serialize(domainEvent),
Status = 0
};
context.Add(outbox);
}
entry.Entity.ClearDomainEvents();
}
return base.SavingChangesAsync(eventData, result, cancellationToken);
}
}
2. Domain Entity Base Class
Domain entities raise events that are captured by the outbox interceptor:
public abstract class DomainEntity
{
private readonly List<IDomainEvent> _events = new();
public IReadOnlyList<IDomainEvent> DomainEvents => _events;
protected void AddDomainEvent(IDomainEvent e) => _events.Add(e);
public void ClearDomainEvents() => _events.Clear();
}
3. Command Handler Flow
The complete flow from command to event publishing:
// 1. Command handler loads aggregate
var order = await _repository.GetByIdAsync(command.OrderId);
// 2. Aggregate raises domain events
order.Complete(); // Internally calls AddDomainEvent(new OrderCompletedEvent(order))
// 3. Command handler calls SaveChangesAsync()
await _dbContext.SaveChangesAsync();
// 4. EF interceptor picks up domain events
// 5. Interceptor writes them to Outbox
// 6. Interceptor clears domain events
// 7. Transaction commits
// 8. Background worker publishes events later
4. Outbox Dispatcher Hosted Service
Background service that processes and publishes outbox messages:
public sealed class OutboxDispatcherHostedService : BackgroundService
{
private readonly IServiceProvider _provider;
public OutboxDispatcherHostedService(IServiceProvider provider)
{
_provider = provider;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using var scope = _provider.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<YourDbContext>();
var bus = scope.ServiceProvider.GetRequiredService<IEventBus>();
var messages = await db.OutboxMessages
.Where(x => x.Status == 0)
.OrderBy(x => x.OccurredOnUtc)
.Take(100)
.ToListAsync(stoppingToken);
foreach (var msg in messages)
{
try
{
var type = _eventTypeRegistry.Resolve(msg.Type);
var @event = JsonSerializer.Deserialize(msg.Payload, type);
await bus.PublishAsync(@event!);
msg.Status = 1;
msg.LastDispatchedOnUtc = DateTime.UtcNow;
}
catch (Exception ex)
{
msg.Status = 2;
msg.LastDispatchError = ex.ToString();
}
}
await db.SaveChangesAsync(stoppingToken);
await Task.Delay(500, stoppingToken);
}
}
}
5. DbContext Example with Inbox/Outbox
public class AccountingDbContext : DbContext
{
public DbSet<OutboxMessage> OutboxMessages => Set<OutboxMessage>();
public DbSet<InboxMessage> InboxMessages => Set<InboxMessage>();
// Your domain entities
public DbSet<Invoice> Invoices => Set<Invoice>();
public DbSet<Payment> Payments => Set<Payment>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyInboxOutbox(); // Applies inbox/outbox configurations
base.OnModelCreating(modelBuilder);
}
}
Technologies
Version History
| Version | Date | Release Notes |
|---|---|---|
| 1.0.0 | 2026-Jan-20 | Initial release |
License
This project is licensed with the MIT license.
Contact
| 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.EntityFrameworkCore (>= 10.0.0)
- Microsoft.EntityFrameworkCore.Relational (>= 10.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 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.