CommunityAbp.AspNetZero.DistributedEventBus.AzureServiceBus
0.11.1
dotnet add package CommunityAbp.AspNetZero.DistributedEventBus.AzureServiceBus --version 0.11.1
NuGet\Install-Package CommunityAbp.AspNetZero.DistributedEventBus.AzureServiceBus -Version 0.11.1
<PackageReference Include="CommunityAbp.AspNetZero.DistributedEventBus.AzureServiceBus" Version="0.11.1" />
<PackageVersion Include="CommunityAbp.AspNetZero.DistributedEventBus.AzureServiceBus" Version="0.11.1" />
<PackageReference Include="CommunityAbp.AspNetZero.DistributedEventBus.AzureServiceBus" />
paket add CommunityAbp.AspNetZero.DistributedEventBus.AzureServiceBus --version 0.11.1
#r "nuget: CommunityAbp.AspNetZero.DistributedEventBus.AzureServiceBus, 0.11.1"
#:package CommunityAbp.AspNetZero.DistributedEventBus.AzureServiceBus@0.11.1
#addin nuget:?package=CommunityAbp.AspNetZero.DistributedEventBus.AzureServiceBus&version=0.11.1
#tool nuget:?package=CommunityAbp.AspNetZero.DistributedEventBus.AzureServiceBus&version=0.11.1
CommunityAbp.DistributedEventBus
Reliable distributed domain events for ABP / AspNet Zero using the Inbox / Outbox pattern and (optionally) Azure Service Bus.
Targets:
- .NET Standard 2.0 (ABP 9.0.0, broad compatibility)
- .NET 10 (ABP 11.3.x, modern runtime)
⚠️ IMPORTANT: AspNetZero Licensed NuGet Feed Required
Consumers of these packages MUST add the AspNetZero licensed NuGet feed. ABP 11.3.x packages are published only to the AspNetZero licensed feed (https://nuget.aspnetzero.com), not to nuget.org.
Setup (local / CI)
Local development:
dotnet nuget add source "https://nuget.aspnetzero.com/<key>/v3/index.json" -n aspnetzero
GitHub Actions CI: Add repository secret ASPNETZERO_NUGET_URL with the feed URL + key, then include:
- name: Add AspNetZero NuGet source
run: dotnet nuget add source "${{ secrets.ASPNETZERO_NUGET_URL }}" -n aspnetzero
IMPORTANT STATUS WARNING
The EntityFrameworkCore persistence module (
CommunityAbp.AspNetZero.DistributedEventBus.EntityFrameworkCore) is experimental. Outbox sending and inbox processing are covered by integration tests, but production durability still requires application-owned retry, monitoring, and deployment validation.Until completed:
- Treat Outbox/Inboxes as experimental.
- Prefer
DistributedEventDispatchMode.Directfor user-facing progress and completion flows.- Expect schema/behavior changes.
A compile-time warning is emitted when referencing the EF module.
Key Features
- Distributed event bus abstraction (
IDistributedEventBus) - Optional Azure Service Bus transport (
AzureServiceBusDistributedEventBus) - Outbox & Inbox persistence abstraction (plug in any storage)
- Configurable multiple Outboxes / Inboxes with filtering predicates
- Polling sender / processor manager abstractions (
IOutboxSender,IInboxProcessor) - Event boxing interfaces (
ISupportsEventBoxes) for replay / reliability - Strongly typed async handlers (
IDistributedEventHandler<TEvent>) - Optional stable logical names via
[EventName]attribute - Manual, explicit subscription model (no auto-discovery / auto-subscribe)
- Test friendly (replace the bus with an in‑memory implementation)
NOTE:
DistributedEventDispatchMode.Outboxpersists an event without immediately sending it. A configuredIOutboxSendermust later publish it.
Architecture Overview
Publisher (Hangfire / API / etc.)
=> IDistributedEventBus.PublishAsync(event, dispatchMode)
(Outbox) -> Persist to Outbox(s) -> Outbox Sender -> Broker (Azure Service Bus)
(Direct) -> Immediate Dispatch -> (local handlers if any) + Direct Broker send (Azure impl)
Azure Service Bus Topic / Queue
-> Consumer application subscription
-> Message received -> (optional Inbox persist) -> Dispatch to manually subscribed handlers
No implicit handler scanning: each consumer process decides which handlers to subscribe.
Manual Subscription Model
Auto-subscribe was removed to:
- Avoid resolving optional dependencies (e.g. SignalR hubs) in publisher processes
- Make handler activation explicit & environment-specific
You must subscribe handlers manually at application initialization:
public override void OnApplicationInitialization(AbpApplicationInitializationContext ctx)
{
var bus = IocManager.Resolve<IDistributedEventBus>();
var hubHandler = IocManager.Resolve<BackgroundJobEventHub>(); // implements IDistributedEventHandler<BackgroundJobEventBase>
bus.Subscribe<BackgroundJobEventBase>(hubHandler);
}
Publisher processes (e.g. Hangfire) only reference event contracts and publish; they do not subscribe SignalR hubs or other consumer-only handlers.
Publisher / Consumer Separation Example
Create a shared contracts library:
public class UserNotificationEvent : EventData
{
public Guid UserId { get; set; }
public string Message { get; set; } = string.Empty;
}
Publisher (Hangfire):
await _bus.PublishAsync(new UserNotificationEvent { UserId = id, Message = "Welcome" }, DistributedEventDispatchMode.Direct);
(No handler subscription; do NOT reference the SignalR hub assembly.)
Consumer (MVC with SignalR):
public class NotificationsHub : Hub, IDistributedEventHandler<UserNotificationEvent>
{
private readonly IHubContext<NotificationsHub> _context;
public NotificationsHub(IHubContext<NotificationsHub> context) => _context = context;
public async Task HandleEventAsync(UserNotificationEvent evt)
=> await _context.Clients.User(evt.UserId.ToString()).SendCoreAsync("notification", new object[] { evt.Message });
}
public class MvcModule : AbpModule
{
public override void OnApplicationInitialization(AbpApplicationInitializationContext context)
{
var bus = IocManager.Resolve<IDistributedEventBus>();
var hub = IocManager.Resolve<NotificationsHub>();
bus.Subscribe<UserNotificationEvent>(hub);
}
}
Packages (Current Layout)
CommunityAbp.AspNetZero.DistributedEventBus.CoreCommunityAbp.AspNetZero.DistributedEventBus.AzureServiceBusCommunityAbp.AspNetZero.DistributedEventBus.EntityFrameworkCore(INCOMPLETE – experimental persistence)- Test infrastructure in
tests/*
Installation (Example)
Add packages:
dotnet add package CommunityAbp.AspNetZero.DistributedEventBus.Core
# Optional
dotnet add package CommunityAbp.AspNetZero.DistributedEventBus.AzureServiceBus
# Persistence (EF module currently incomplete / experimental)
dotnet add package CommunityAbp.AspNetZero.DistributedEventBus.EntityFrameworkCore
Register ABP modules (consumer example):
[DependsOn(
typeof(AspNetZeroDistributedEventBusModule),
typeof(AzureDistributedEventServiceBusModule), // Azure transport
typeof(AspNetZeroDistributedEventEntityFrameworkCoreModule) // EF persistence (optional / experimental)
)]
public class ConsumerModule : AbpModule
{
public override void PreInitialize()
{
var options = IocManager.Resolve<DistributedEventBusOptions>();
options.Inboxes.Configure("Default", i =>
{
i.ImplementationType = typeof(EfCoreEventInbox);
i.EventSelector = _ => true;
i.IsProcessingEnabled = true; // experimental
});
}
public override void OnApplicationInitialization(AbpApplicationInitializationContext ctx)
{
var bus = IocManager.Resolve<IDistributedEventBus>();
var hub = IocManager.Resolve<NotificationsHub>();
bus.Subscribe<UserNotificationEvent>(hub);
}
}
appsettings.json snippet for Azure Service Bus:
"AzureServiceBus": {
"ConnectionString": "Endpoint=sb://<namespace>.servicebus.windows.net/;SharedAccessKeyName=<KeyName>;SharedAccessKey=<Key>",
"EntityPath": "app-events", // topic or queue
"SubscriptionName": "consumer-app" // required for topic subscriptions
}
Using Your Production DbContext For Migrations (Recommended)
(Same as previous version; unchanged. EF inbox/outbox tables still subject to change.)
- Reference the EF package.
- Add DbSets to your DbContext:
public DbSet<OutboxMessage> OutboxMessages { get; set; }
public DbSet<InboxMessage> InboxMessages { get; set; }
- Call configuration in
OnModelCreating:
modelBuilder.ConfigureDistributedEventBus();
- Generate & apply migrations from your app's data project.
Production Setup (Summary)
- Register modules.
- Configure Outboxes / Inboxes.
- Add hosted service to start polling (
IOutboxSender,IInboxProcessor). - Manually subscribe handlers at startup.
- Publish events from publishers (no handler dependencies required).
Hosted service sample (unchanged):
public class EventBoxesHostedService : IHostedService
{
private readonly IOutboxSender _outbox;
private readonly IInboxProcessor _inbox;
private readonly DistributedEventBusOptions _opts;
public EventBoxesHostedService(IOutboxSender o, IInboxProcessor i, DistributedEventBusOptions opts)
{ _outbox = o; _inbox = i; _opts = opts; }
public async Task StartAsync(CancellationToken ct)
{
foreach (var ob in _opts.Outboxes.Values.Where(x => x.IsSendingEnabled)) await _outbox.StartAsync(ob);
foreach (var ib in _opts.Inboxes.Values.Where(x => x.IsProcessingEnabled)) await _inbox.StartAsync(ib);
}
public async Task StopAsync(CancellationToken ct)
{ await _outbox.StopAsync(); await _inbox.StopAsync(); }
}
Defining & Handling Events
[EventName("Orders.Created")]
public class OrderCreatedEvent : EventData { public Guid OrderId { get; set; } public decimal Total { get; set; } }
public class OrderCreatedHandler : IDistributedEventHandler<OrderCreatedEvent>
{
public Task HandleEventAsync(OrderCreatedEvent evt)
{ /* side effects */ return Task.CompletedTask; }
}
Publishing
await _bus.PublishAsync(new OrderCreatedEvent { OrderId = order.Id, Total = order.Total }, DistributedEventDispatchMode.Direct);
Manual Subscription
_bus.Subscribe(new OrderCreatedHandler());
Unsubscribe via the returned IDisposable.
Azure Service Bus Specifics
- Requires valid
ConnectionString+EntityPath; useEntityKind = TopicwithSubscriptionNamefor topics, orEntityKind = Queuewithout a subscription for queues. - Messages carry a stable
[EventName]logical identifier plus a legacy CLR identifier for rolling-deploy compatibility. - If an inbox (
IEventInbox) is injected into the Azure bus, it will persist incoming messages before handler invocation (experimental when using EF module). - Queue mode: set
EntityPathto queue name and omitSubscriptionName(adjust the processor code if needed). - Lifetime (v0.11.0+): the bus is registered as a singleton: one
ServiceBusClient, oneServiceBusSender, at most oneServiceBusProcessorper process. Do not override the lifetime.Dispose()/DisposeAsync()release the connection and are idempotent. - No in-process echo (v0.11.0+): a
Directpublish from the Azure bus does not invoke local handlers; the broker copy does, exactly once, withMessageId,EntityPath,SubscriptionNameandDeliveryCountinIDistributedEventContextAccessor.Current. Handlers do not need to filter out echoes. See docs/singleton-bus-and-local-dispatch.md.
Idempotency & Duplicates
Use Inbox storage (experimental), or register IIncomingMessageDeduplicator for application-owned duplicate suppression keyed by broker MessageId.
Testing
Manual subscription pattern:
var bus = Resolve<IDistributedEventBus>();
var handled = false;
var sub = bus.Subscribe(new OrderCreatedHandler(() => handled = true));
await bus.PublishAsync(new OrderCreatedEvent { OrderId = Guid.NewGuid() }, DistributedEventDispatchMode.Direct);
Assert.True(handled);
Extension Points
| Interface | Purpose |
|---|---|
IEventOutbox |
Persist outgoing events. |
IEventInbox |
Persist incoming broker events. |
IOutboxSender |
Poll / send pending outbox events. |
IInboxProcessor |
Poll / process pending inbox events. |
IDistributedEventBus |
Replace transport (RabbitMQ, Kafka, etc.). |
Correlation & Tracing
OutgoingEventInfo / IncomingEventInfo expose correlation helpers; attach IDs before persistence.
Roadmap / Ideas
- Complete EF persistence reliability path
- Respect
onUnitOfWorkComplete - Retry / exponential backoff
- Batch operations
- Instrumentation (ActivitySource)
- Additional transports (RabbitMQ, Kafka, Event Hubs)
Limitations (Current)
- EF persistence incomplete
- Azure bus publishes immediately (even if outbox used)
- No built-in hosted service registration (sample provided)
- Manual subscription required (intentional design)
Event Naming
[EventName] provides stable logical names; fallback is FullName. Register public contract types and legacy aliases in every consumer during a rolling deployment. See MendMD migration.
Contributing
- Fork & clone
- Create feature branch
- Add tests
- Submit PR
License
MIT ©2025 Kori Francis
References
- Azure Service Bus: https://learn.microsoft.com/azure/service-bus-messaging/
- Transactional Outbox Pattern: https://microservices.io/patterns/data/transactional-outbox.html
- ABP Framework: https://abp.io/
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. 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 was computed. 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. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Azure.Messaging.ServiceBus (>= 7.20.2)
- CommunityAbp.AspNetZero.DistributedEventBus.Core (>= 0.11.1)
-
net10.0
- Azure.Messaging.ServiceBus (>= 7.20.2)
- CommunityAbp.AspNetZero.DistributedEventBus.Core (>= 0.11.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.