Browse Source

Merge pull request #25023 from abpframework/issue-24918-event-bus

String-Based Event Publishing with Dynamic Payload
pull/25151/head
SALİH ÖZKARA 6 days ago
committed by GitHub
parent
commit
a172651d44
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 3
      .claude/settings.local.json
  2. 203
      docs/en/Community-Articles/2026-03-23-Dynamic-Events-in-ABP/POST.md
  3. BIN
      docs/en/Community-Articles/2026-03-23-Dynamic-Events-in-ABP/cover.png
  4. 76
      docs/en/framework/infrastructure/event-bus/distributed/index.md
  5. 53
      docs/en/framework/infrastructure/event-bus/local/index.md
  6. 42
      framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/IDistributedEventBus.cs
  7. 17
      framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/DynamicEventData.cs
  8. 46
      framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/IEventBus.cs
  9. 7
      framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Local/ILocalEventBus.cs
  10. 315
      framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs
  11. 114
      framework/src/Volo.Abp.EventBus.Dapr/Volo/Abp/EventBus/Dapr/DaprDistributedEventBus.cs
  12. 143
      framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs
  13. 146
      framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs
  14. 297
      framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs
  15. 71
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs
  16. 122
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus.cs
  17. 46
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/NullDistributedEventBus.cs
  18. 94
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs
  19. 22
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventHandlerFactoryUnregistrar.cs
  20. 123
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventBus.cs
  21. 40
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/NullLocalEventBus.cs
  22. 280
      framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus_Test.cs
  23. 258
      framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Local/LocalEventBus_Dynamic_Test.cs
  24. 28
      test/DistEvents/DistDemoApp.EfCoreRabbitMq/DistDemoApp.EfCoreRabbitMq.csproj
  25. 44
      test/DistEvents/DistDemoApp.EfCoreRabbitMq/DistDemoAppEfCoreRabbitMqModule.cs
  26. 162
      test/DistEvents/DistDemoApp.EfCoreRabbitMq/Migrations/20210910152547_Added_Boxes_Initial.Designer.cs
  27. 103
      test/DistEvents/DistDemoApp.EfCoreRabbitMq/Migrations/20210910152547_Added_Boxes_Initial.cs
  28. 160
      test/DistEvents/DistDemoApp.EfCoreRabbitMq/Migrations/TodoDbContextModelSnapshot.cs
  29. 57
      test/DistEvents/DistDemoApp.EfCoreRabbitMq/Program.cs
  30. 34
      test/DistEvents/DistDemoApp.EfCoreRabbitMq/TodoDbContext.cs
  31. 29
      test/DistEvents/DistDemoApp.EfCoreRabbitMq/TodoDbContextFactory.cs
  32. 19
      test/DistEvents/DistDemoApp.EfCoreRabbitMq/appsettings.json
  33. 21
      test/DistEvents/DistDemoApp.MongoDbKafka/DistDemoApp.MongoDbKafka.csproj
  34. 38
      test/DistEvents/DistDemoApp.MongoDbKafka/DistDemoAppMongoDbKafkaModule.cs
  35. 57
      test/DistEvents/DistDemoApp.MongoDbKafka/Program.cs
  36. 26
      test/DistEvents/DistDemoApp.MongoDbKafka/TodoMongoDbContext.cs
  37. 19
      test/DistEvents/DistDemoApp.MongoDbKafka/appsettings.json
  38. 21
      test/DistEvents/DistDemoApp.MongoDbRebus/DistDemoApp.MongoDbRebus.csproj
  39. 53
      test/DistEvents/DistDemoApp.MongoDbRebus/DistDemoAppMongoDbRebusModule.cs
  40. 57
      test/DistEvents/DistDemoApp.MongoDbRebus/Program.cs
  41. 19
      test/DistEvents/DistDemoApp.MongoDbRebus/TodoMongoDbContext.cs
  42. 19
      test/DistEvents/DistDemoApp.MongoDbRebus/appsettings.json
  43. 29
      test/DistEvents/DistDemoApp.Shared/DemoService.cs
  44. 23
      test/DistEvents/DistDemoApp.Shared/DistDemoApp.Shared.csproj
  45. 39
      test/DistEvents/DistDemoApp.Shared/DistDemoAppHostedService.cs
  46. 39
      test/DistEvents/DistDemoApp.Shared/DistDemoAppSharedModule.cs
  47. 63
      test/DistEvents/DistDemoApp.Shared/TodoEventHandler.cs
  48. 15
      test/DistEvents/DistDemoApp.Shared/TodoItem.cs
  49. 12
      test/DistEvents/DistDemoApp.Shared/TodoItemEto.cs
  50. 24
      test/DistEvents/DistDemoApp.Shared/TodoItemObjectMapper.cs
  51. 41
      test/DistEvents/DistDemoApp.Shared/TodoSummary.cs
  52. 6
      test/DistEvents/DistEventsDemo.slnx

3
.claude/settings.local.json

@ -2,7 +2,8 @@
"permissions": {
"allow": [
"Bash(yarn nx g:*)",
"Bash(npx vitest:*)"
"Bash(npx vitest:*)",
"Bash(git show:*)"
]
}
}

203
docs/en/Community-Articles/2026-03-23-Dynamic-Events-in-ABP/POST.md

@ -0,0 +1,203 @@
# Dynamic Events in ABP
ABP's Event Bus is a core infrastructure piece. The **Local Event Bus** handles in-process communication between services. The **Distributed Event Bus** handles cross-service communication over message brokers like RabbitMQ, Kafka, Azure Service Bus, and Rebus.
Both are fully type-safe — you define event types at compile time, register handlers via DI, and everything is wired up automatically. This works great, but it has one assumption: **you know all your event types at compile time**.
In practice, that assumption breaks down in several scenarios:
- You're building a **plugin system** where third-party modules register their own event types at runtime — you can't pre-define an `IDistributedEventHandler<TEvent>` for every possible plugin event
- Your system receives events from **external systems** (webhooks, IoT devices, partner APIs) where the event schema is defined by the external party, not by your codebase
- You're building a **low-code platform** where end users define event-driven workflows through a visual designer — the event names and payloads are entirely determined at runtime
ABP's **Dynamic Events** extend the existing `IEventBus` and `IDistributedEventBus` interfaces with string-based publishing and subscription. You can publish events by name, subscribe to events by name, and handle payloads without any compile-time type binding — all while coexisting seamlessly with the existing typed event system.
## Publishing Events by Name
The most straightforward use case: publish an event using a string name and an arbitrary payload.
```csharp
public class OrderAppService : ApplicationService
{
private readonly IDistributedEventBus _eventBus;
public OrderAppService(IDistributedEventBus eventBus)
{
_eventBus = eventBus;
}
public async Task PlaceOrderAsync(PlaceOrderInput input)
{
// Business logic...
// Publish a dynamic event — no event class needed
await _eventBus.PublishAsync(
"OrderPlaced",
new { OrderId = input.Id, CustomerEmail = input.Email }
);
}
}
```
The payload can be any serializable object — an anonymous type, a `Dictionary<string, object>`, or even an existing typed class. The event bus serializes the payload and sends it to the broker with the string name as the routing key.
### What If a Typed Event Already Exists?
If the string name matches an existing typed event (via `EventNameAttribute`), the framework automatically converts the payload to the typed class and routes it through the **typed pipeline**. Both typed handlers and dynamic handlers are triggered.
```csharp
[EventName("OrderPlaced")]
public class OrderPlacedEto
{
public Guid OrderId { get; set; }
public string CustomerEmail { get; set; }
}
// This handler will still receive the event, with auto-converted data
public class OrderEmailHandler : IDistributedEventHandler<OrderPlacedEto>
{
public Task HandleEventAsync(OrderPlacedEto eventData)
{
// eventData.OrderId and eventData.CustomerEmail are populated
return Task.CompletedTask;
}
}
```
Publishing by name with `new { OrderId = ..., CustomerEmail = ... }` triggers this typed handler — the framework handles the serialization round-trip. This is especially useful for scenarios where a service needs to emit events without taking a dependency on the project that defines the event type.
## Subscribing to Dynamic Events
Dynamic subscription lets you register event handlers at runtime, using a string event name.
```csharp
public override async Task OnApplicationInitializationAsync(
ApplicationInitializationContext context)
{
var eventBus = context.ServiceProvider
.GetRequiredService<IDistributedEventBus>();
// Subscribe to a dynamic event — no event class needed
eventBus.Subscribe("PartnerOrderReceived",
new PartnerOrderHandler(context.ServiceProvider));
}
```
The handler implements `IDistributedEventHandler<DynamicEventData>`:
```csharp
public class PartnerOrderHandler : IDistributedEventHandler<DynamicEventData>
{
private readonly IServiceProvider _serviceProvider;
public PartnerOrderHandler(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task HandleEventAsync(DynamicEventData eventData)
{
// eventData.EventName = "PartnerOrderReceived"
// eventData.Data = the raw payload from the broker
var orderProcessor = _serviceProvider
.GetRequiredService<IPartnerOrderProcessor>();
await orderProcessor.ProcessAsync(eventData.EventName, eventData.Data);
}
}
```
`DynamicEventData` is a simple POCO with two properties:
- **`EventName`** — the string name that identifies the event
- **`Data`** — the raw event data payload (the deserialized `object` from the broker)
> `Subscribe` returns an `IDisposable`. Call `Dispose()` to unsubscribe the handler at runtime.
## Mixed Typed and Dynamic Handlers
Typed and dynamic handlers coexist naturally. When both are registered for the same event name, **both are triggered** — the framework automatically converts the data to the appropriate format for each handler.
```csharp
// Typed handler — receives OrderPlacedEto
eventBus.Subscribe<OrderPlacedEto, OrderEmailHandler>();
// Dynamic handler — receives DynamicEventData for the same event
eventBus.Subscribe("OrderPlaced", new AuditLogHandler());
```
When `OrderPlacedEto` is published (by type or by name), both handlers fire. The typed handler receives a fully deserialized `OrderPlacedEto` object. The dynamic handler receives a `DynamicEventData` wrapping the raw payload.
This enables a powerful pattern: the core business logic uses typed handlers for safety, while infrastructure concerns (auditing, logging, plugin hooks) use dynamic handlers for flexibility.
## Outbox Support
Dynamic events go through the same **outbox/inbox pipeline** as typed events. If you have outbox configured, dynamic events benefit from the same reliability guarantees — they are stored in the outbox table within the same database transaction as your business data, then reliably delivered to the broker by the background worker.
No additional configuration is needed. The outbox works transparently for both typed and dynamic events:
```csharp
// This dynamic event goes through the outbox if configured
using var uow = _unitOfWorkManager.Begin();
await _eventBus.PublishAsync(
"OrderPlaced",
new { OrderId = orderId },
onUnitOfWorkComplete: true,
useOutbox: true
);
await uow.CompleteAsync();
```
## Local Event Bus
Dynamic events work on the local event bus too, not just the distributed bus. The API is the same:
```csharp
var localEventBus = context.ServiceProvider
.GetRequiredService<ILocalEventBus>();
// Subscribe dynamically
localEventBus.Subscribe("UserActivityTracked",
new SingleInstanceHandlerFactory(
new ActionEventHandler<DynamicEventData>(eventData =>
{
// Handle the event
return Task.CompletedTask;
})));
// Publish dynamically
await localEventBus.PublishAsync("UserActivityTracked", new
{
UserId = currentUser.Id,
Action = "PageView",
Url = "/products/42"
});
```
## Provider Support
Dynamic events work with all distributed event bus providers:
| Provider | Dynamic Subscribe | Dynamic Publish |
|---|---|---|
| LocalDistributedEventBus (default) | ✅ | ✅ |
| RabbitMQ | ✅ | ✅ |
| Kafka | ✅ | ✅ |
| Rebus | ✅ | ✅ |
| Azure Service Bus | ✅ | ✅ |
| Dapr | ❌ | ❌ |
Dapr requires topic subscriptions to be declared at application startup and cannot add subscriptions at runtime. Calling `Subscribe(string, ...)` on the Dapr provider throws an `AbpException`.
## Summary
`IEventBus.PublishAsync(string, object)` and `IEventBus.Subscribe(string, handler)` let you publish and subscribe to events by name at runtime — no compile-time types required. If the event name matches a typed event, the framework auto-converts the payload and triggers both typed and dynamic handlers. Dynamic events go through the same outbox/inbox pipeline as typed events, so reliability guarantees are preserved. This works across all providers except Dapr, and coexists seamlessly with the existing typed event system.
## References
- [Local Event Bus](https://abp.io/docs/latest/framework/infrastructure/event-bus/local)
- [Distributed Event Bus](https://abp.io/docs/latest/framework/infrastructure/event-bus/distributed)
- [RabbitMQ Integration](https://abp.io/docs/latest/framework/infrastructure/event-bus/distributed/rabbitmq)
- [Kafka Integration](https://abp.io/docs/latest/framework/infrastructure/event-bus/distributed/kafka)
- [Dynamic Distributed Events Sample](https://github.com/abpframework/abp-samples/tree/master/DynamicDistributedEvents)

BIN
docs/en/Community-Articles/2026-03-23-Dynamic-Events-in-ABP/cover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 KiB

76
docs/en/framework/infrastructure/event-bus/distributed/index.md

@ -721,6 +721,82 @@ Configure<AbpDistributedEventBusOptions>(options =>
});
````
## Dynamic (String-Based) Events
In addition to the type-safe event system described above, ABP also supports **dynamic events** that are identified by a string name rather than a CLR type. This is useful for scenarios where event types are not known at compile time, such as integrating with external systems or building plugin architectures.
> **Note:** Dynamic event subscriptions are supported by RabbitMQ, Kafka, Azure Service Bus, and Rebus providers. The **Dapr provider does not support dynamic events** because Dapr requires topic subscriptions to be declared at application startup and cannot add subscriptions at runtime. Attempting to call `Subscribe(string, ...)` on the Dapr provider will throw an `AbpException`.
### Publishing Dynamic Events
Use the `PublishAsync` overload that accepts a string event name:
````csharp
await distributedEventBus.PublishAsync(
"MyDynamicEvent",
new Dictionary<string, object>
{
["UserId"] = 42,
["Name"] = "John"
}
);
````
If a typed event exists with the given name (via `EventNameAttribute` or convention), the data is automatically deserialized and routed to the typed handler. Otherwise, it is delivered as a `DynamicEventData` to dynamic handlers.
You can also control `onUnitOfWorkComplete` and `useOutbox` parameters:
````csharp
await distributedEventBus.PublishAsync(
"MyDynamicEvent",
new { UserId = 42, Name = "John" },
onUnitOfWorkComplete: true,
useOutbox: true
);
````
### Subscribing to Dynamic Events
Use the `Subscribe` overload that accepts a string event name:
````csharp
var subscription = distributedEventBus.Subscribe(
"MyDynamicEvent",
new SingleInstanceHandlerFactory(
new ActionEventHandler<DynamicEventData>(eventData =>
{
// Access the event name and raw data
var name = eventData.EventName;
var data = eventData.Data;
return Task.CompletedTask;
})));
// Unsubscribe when done
subscription.Dispose();
````
You can also subscribe using a typed distributed event handler:
````csharp
distributedEventBus.Subscribe("MyDynamicEvent", myDistributedEventHandler);
````
Where `myDistributedEventHandler` implements `IDistributedEventHandler<DynamicEventData>`.
### Mixed Typed and Dynamic Handlers
When both a typed handler and a dynamic handler are registered for the same event name, **both** handlers are triggered. The typed handler receives the converted typed data, while the dynamic handler receives a `DynamicEventData` wrapper.
### DynamicEventData Class
The `DynamicEventData` class is a simple data object that wraps the event payload:
- **`EventName`**: The string name that identifies the event.
- **`Data`**: The raw event data payload.
> If a typed handler exists for the same event name, the framework automatically converts the data to the expected type using the event bus serialization pipeline. Dynamic handlers receive the raw `Data` as-is.
## See Also
* [Local Event Bus](../local)

53
docs/en/framework/infrastructure/event-bus/local/index.md

@ -249,6 +249,59 @@ If you set it to `false`, the `EntityUpdatedEventData<T>` will not be published
> This option is only used for the EF Core.
## Dynamic (String-Based) Events
In addition to the type-safe event system described above, ABP also supports **dynamic events** that are identified by a string name rather than a CLR type. This is useful for scenarios where event types are not known at compile time.
### Publishing Dynamic Events
Use the `PublishAsync` overload that accepts a string event name:
````csharp
await localEventBus.PublishAsync(
"MyDynamicEvent",
new Dictionary<string, object>
{
["UserId"] = 42,
["Name"] = "John"
}
);
````
If a typed event exists with the given name (via `EventNameAttribute` or convention), the data is automatically converted and routed to the typed handler. Otherwise, it is delivered as a `DynamicEventData` to dynamic handlers.
### Subscribing to Dynamic Events
Use the `Subscribe` overload that accepts a string event name:
````csharp
var subscription = localEventBus.Subscribe(
"MyDynamicEvent",
new SingleInstanceHandlerFactory(
new ActionEventHandler<DynamicEventData>(eventData =>
{
// Access the event name and raw data
var name = eventData.EventName;
var data = eventData.Data;
return Task.CompletedTask;
})));
// Unsubscribe when done
subscription.Dispose();
````
The `DynamicEventData` class is a simple data object with two properties:
- **`EventName`**: The string name that identifies the event.
- **`Data`**: The raw event data payload.
> If a typed handler exists for the same event name, the framework automatically converts the data to the expected type. Dynamic handlers receive the raw `Data` as-is.
### Mixed Typed and Dynamic Handlers
When both a typed handler and a dynamic handler are registered for the same event name, **both** handlers are triggered. The typed handler receives the converted typed data, while the dynamic handler receives a `DynamicEventData` wrapper.
## See Also
* [Distributed Event Bus](../distributed)

42
framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Distributed/IDistributedEventBus.cs

@ -1,4 +1,4 @@
using System;
using System;
using System.Threading.Tasks;
namespace Volo.Abp.EventBus.Distributed;
@ -14,15 +14,55 @@ public interface IDistributedEventBus : IEventBus
IDisposable Subscribe<TEvent>(IDistributedEventHandler<TEvent> handler)
where TEvent : class;
/// <summary>
/// Triggers an event.
/// </summary>
/// <typeparam name="TEvent">Event type</typeparam>
/// <param name="eventData">Related data for the event</param>
/// <param name="onUnitOfWorkComplete">True, to publish the event at the end of the current unit of work, if available</param>
/// <param name="useOutbox">True, to use the outbox pattern for reliable event publishing</param>
/// <returns>The task to handle async operation</returns>
Task PublishAsync<TEvent>(
TEvent eventData,
bool onUnitOfWorkComplete = true,
bool useOutbox = true)
where TEvent : class;
/// <summary>
/// Triggers an event.
/// </summary>
/// <param name="eventType">Event type</param>
/// <param name="eventData">Related data for the event</param>
/// <param name="onUnitOfWorkComplete">True, to publish the event at the end of the current unit of work, if available</param>
/// <param name="useOutbox">True, to use the outbox pattern for reliable event publishing</param>
/// <returns>The task to handle async operation</returns>
Task PublishAsync(
Type eventType,
object eventData,
bool onUnitOfWorkComplete = true,
bool useOutbox = true);
/// <summary>
/// Registers to an event by its string-based event name.
/// Same (given) instance of the handler is used for all event occurrences.
/// Wraps the handler as <see cref="IDistributedEventHandler{DynamicEventData}"/>.
/// </summary>
/// <param name="eventName">Name of the event</param>
/// <param name="handler">Object to handle the event</param>
IDisposable Subscribe(string eventName, IDistributedEventHandler<DynamicEventData> handler);
/// <summary>
/// Triggers an event by its string-based event name.
/// Used for dynamic (type-less) event publishing over distributed event bus.
/// </summary>
/// <param name="eventName">Name of the event</param>
/// <param name="eventData">Related data for the event</param>
/// <param name="onUnitOfWorkComplete">True, to publish the event at the end of the current unit of work, if available</param>
/// <param name="useOutbox">True, to use the outbox pattern for reliable event publishing</param>
/// <returns>The task to handle async operation</returns>
Task PublishAsync(
string eventName,
object eventData,
bool onUnitOfWorkComplete = true,
bool useOutbox = true);
}

17
framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/DynamicEventData.cs

@ -0,0 +1,17 @@
namespace Volo.Abp.EventBus;
/// <summary>
/// Wraps arbitrary event data with a string-based event name for dynamic (type-less) event handling.
/// </summary>
public class DynamicEventData
{
public string EventName { get; }
public object Data { get; }
public DynamicEventData(string eventName, object data)
{
EventName = Check.NotNullOrWhiteSpace(eventName, nameof(eventName));
Data = Check.NotNull(data, nameof(data));
}
}

46
framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/IEventBus.cs

@ -23,6 +23,16 @@ public interface IEventBus
/// <param name="onUnitOfWorkComplete">True, to publish the event at the end of the current unit of work, if available</param>
/// <returns>The task to handle async operation</returns>
Task PublishAsync(Type eventType, object eventData, bool onUnitOfWorkComplete = true);
/// <summary>
/// Triggers an event by its string-based event name.
/// Used for dynamic (type-less) event publishing.
/// </summary>
/// <param name="eventName">Name of the event</param>
/// <param name="eventData">Related data for the event</param>
/// <param name="onUnitOfWorkComplete">True, to publish the event at the end of the current unit of work, if available</param>
/// <returns>The task to handle async operation</returns>
Task PublishAsync(string eventName, object eventData, bool onUnitOfWorkComplete = true);
/// <summary>
/// Registers to an event.
@ -50,6 +60,22 @@ public interface IEventBus
/// <param name="eventType">Event type</param>
/// <param name="handler">Object to handle the event</param>
IDisposable Subscribe(Type eventType, IEventHandler handler);
/// <summary>
/// Registers to an event by its string-based event name.
/// Same (given) instance of the handler is used for all event occurrences.
/// </summary>
/// <param name="eventName">Name of the event</param>
/// <param name="handler">Object to handle the event</param>
IDisposable Subscribe(string eventName, IEventHandler handler);
/// <summary>
/// Registers to an event by its string-based event name.
/// Given factory is used to create/release handlers.
/// </summary>
/// <param name="eventName">Name of the event</param>
/// <param name="handler">A factory to create/release handlers</param>
IDisposable Subscribe(string eventName, IEventHandlerFactory handler);
/// <summary>
/// Registers to an event.
@ -104,6 +130,20 @@ public interface IEventBus
/// <param name="eventType">Event type</param>
/// <param name="factory">Factory object that is registered before</param>
void Unsubscribe(Type eventType, IEventHandlerFactory factory);
/// <summary>
/// Unregisters from an event by its string-based event name.
/// </summary>
/// <param name="eventName">Name of the event</param>
/// <param name="factory">Factory object that is registered before</param>
void Unsubscribe(string eventName, IEventHandlerFactory factory);
/// <summary>
/// Unregisters from an event by its string-based event name.
/// </summary>
/// <param name="eventName">Name of the event</param>
/// <param name="handler">Handler object that is registered before</param>
void Unsubscribe(string eventName, IEventHandler handler);
/// <summary>
/// Unregisters all event handlers of given event type.
@ -117,4 +157,10 @@ public interface IEventBus
/// </summary>
/// <param name="eventType">Event type</param>
void UnsubscribeAll(Type eventType);
/// <summary>
/// Unregisters all event handlers of given string-based event name.
/// </summary>
/// <param name="eventName">Name of the event</param>
void UnsubscribeAll(string eventName);
}

7
framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/Local/ILocalEventBus.cs

@ -23,4 +23,11 @@ public interface ILocalEventBus : IEventBus
/// <param name="eventType">Event type</param>
/// <returns></returns>
List<EventTypeWithEventHandlerFactories> GetEventHandlerFactories(Type eventType);
/// <summary>
/// Gets the list of event handler factories for the given string-based event name.
/// </summary>
/// <param name="eventName">Name of the event</param>
/// <returns>List of event handler factories registered for the given event name</returns>
List<EventTypeWithEventHandlerFactories> GetDynamicEventHandlerFactories(string eventName);
}

315
framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs

@ -29,6 +29,7 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen
protected IAzureServiceBusSerializer Serializer { get; }
protected ConcurrentDictionary<Type, List<IEventHandlerFactory>> HandlerFactories { get; }
protected ConcurrentDictionary<string, Type> EventTypes { get; }
protected ConcurrentDictionary<string, List<IEventHandlerFactory>> DynamicHandlerFactories { get; }
protected IAzureServiceBusMessageConsumer Consumer { get; private set; } = default!;
public AzureDistributedEventBus(
@ -61,6 +62,7 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen
PublisherPool = publisherPool;
HandlerFactories = new ConcurrentDictionary<Type, List<IEventHandlerFactory>>();
EventTypes = new ConcurrentDictionary<string, Type>();
DynamicHandlerFactories = new ConcurrentDictionary<string, List<IEventHandlerFactory>>();
}
public void Initialize()
@ -81,14 +83,25 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen
{
return;
}
var eventType = EventTypes.GetOrDefault(eventName);
if (eventType == null)
object eventData;
if (eventType != null)
{
eventData = Serializer.Deserialize(message.Body.ToArray(), eventType);
}
else if (DynamicHandlerFactories.ContainsKey(eventName))
{
var rawBytes = message.Body.ToArray();
eventData = new DynamicEventData(eventName, Serializer.Deserialize<object>(rawBytes));
eventType = typeof(DynamicEventData);
}
else
{
return;
}
var eventData = Serializer.Deserialize(message.Body.ToArray(), eventType);
if (await AddToInboxAsync(message.MessageId, eventName, eventType, eventData, message.CorrelationId))
{
return;
@ -100,6 +113,113 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen
}
}
public override IDisposable Subscribe(Type eventType, IEventHandlerFactory factory)
{
var handlerFactories = GetOrCreateHandlerFactories(eventType);
if (factory.IsInFactories(handlerFactories))
{
return NullDisposable.Instance;
}
handlerFactories.Add(factory);
return new EventHandlerFactoryUnregistrar(this, eventType, factory);
}
/// <inheritdoc/>
public override IDisposable Subscribe(string eventName, IEventHandlerFactory handler)
{
var handlerFactories = GetOrCreateDynamicHandlerFactories(eventName);
if (handler.IsInFactories(handlerFactories))
{
return NullDisposable.Instance;
}
handlerFactories.Add(handler);
return new DynamicEventHandlerFactoryUnregistrar(this, eventName, handler);
}
public override void Unsubscribe<TEvent>(Func<TEvent, Task> action)
{
Check.NotNull(action, nameof(action));
GetOrCreateHandlerFactories(typeof(TEvent))
.Locking(factories =>
{
factories.RemoveAll(
factory =>
{
var singleInstanceFactory = factory as SingleInstanceHandlerFactory;
if (singleInstanceFactory == null)
{
return false;
}
var actionHandler = singleInstanceFactory.HandlerInstance as ActionEventHandler<TEvent>;
if (actionHandler == null)
{
return false;
}
return actionHandler.Action == action;
});
});
}
public override void Unsubscribe(Type eventType, IEventHandler handler)
{
GetOrCreateHandlerFactories(eventType)
.Locking(factories =>
{
factories.RemoveAll(
factory =>
factory is SingleInstanceHandlerFactory handlerFactory &&
handlerFactory.HandlerInstance == handler
);
});
}
public override void Unsubscribe(Type eventType, IEventHandlerFactory factory)
{
GetOrCreateHandlerFactories(eventType)
.Locking(factories => factories.Remove(factory));
}
/// <inheritdoc/>
public override void UnsubscribeAll(Type eventType)
{
GetOrCreateHandlerFactories(eventType)
.Locking(factories => factories.Clear());
}
/// <inheritdoc/>
public override Task PublishAsync(string eventName, object eventData, bool onUnitOfWorkComplete = true)
{
var eventType = EventTypes.GetOrDefault(eventName);
var dynamicEventData = eventData as DynamicEventData ?? new DynamicEventData(eventName, eventData);
if (eventType != null)
{
return PublishAsync(eventType, ConvertDynamicEventData(dynamicEventData.Data, eventType), onUnitOfWorkComplete);
}
return PublishAsync(typeof(DynamicEventData), dynamicEventData, onUnitOfWorkComplete);
}
protected async override Task PublishToEventBusAsync(Type eventType, object eventData)
{
var (eventName, resolvedData) = ResolveEventForPublishing(eventType, eventData);
await PublishAsync(eventName, resolvedData);
}
protected override void AddToUnitOfWork(IUnitOfWork unitOfWork, UnitOfWorkEventRecord eventRecord)
{
unitOfWork.AddOrReplaceDistributedEvent(eventRecord);
}
public async override Task PublishFromOutboxAsync(OutgoingEventInfo outgoingEvent, OutboxConfig outboxConfig)
{
await PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData, outgoingEvent.GetCorrelationId(), outgoingEvent.Id);
@ -162,12 +282,21 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen
public async override Task ProcessFromInboxAsync(IncomingEventInfo incomingEvent, InboxConfig inboxConfig)
{
var eventType = EventTypes.GetOrDefault(incomingEvent.EventName);
if (eventType == null)
object eventData;
if (eventType != null)
{
eventData = Serializer.Deserialize(incomingEvent.EventData, eventType);
}
else if (DynamicHandlerFactories.ContainsKey(incomingEvent.EventName))
{
eventData = new DynamicEventData(incomingEvent.EventName, Serializer.Deserialize<object>(incomingEvent.EventData));
eventType = typeof(DynamicEventData);
}
else
{
return;
}
var eventData = Serializer.Deserialize(incomingEvent.EventData, eventType);
var exceptions = new List<Exception>();
using (CorrelationIdProvider.Change(incomingEvent.GetCorrelationId()))
{
@ -184,82 +313,6 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen
return Serializer.Serialize(eventData);
}
public override IDisposable Subscribe(Type eventType, IEventHandlerFactory factory)
{
var handlerFactories = GetOrCreateHandlerFactories(eventType);
if (factory.IsInFactories(handlerFactories))
{
return NullDisposable.Instance;
}
handlerFactories.Add(factory);
return new EventHandlerFactoryUnregistrar(this, eventType, factory);
}
public override void Unsubscribe<TEvent>(Func<TEvent, Task> action)
{
Check.NotNull(action, nameof(action));
GetOrCreateHandlerFactories(typeof(TEvent))
.Locking(factories =>
{
factories.RemoveAll(
factory =>
{
var singleInstanceFactory = factory as SingleInstanceHandlerFactory;
if (singleInstanceFactory == null)
{
return false;
}
var actionHandler = singleInstanceFactory.HandlerInstance as ActionEventHandler<TEvent>;
if (actionHandler == null)
{
return false;
}
return actionHandler.Action == action;
});
});
}
public override void Unsubscribe(Type eventType, IEventHandler handler)
{
GetOrCreateHandlerFactories(eventType)
.Locking(factories =>
{
factories.RemoveAll(
factory =>
factory is SingleInstanceHandlerFactory handlerFactory &&
handlerFactory.HandlerInstance == handler
);
});
}
public override void Unsubscribe(Type eventType, IEventHandlerFactory factory)
{
GetOrCreateHandlerFactories(eventType)
.Locking(factories => factories.Remove(factory));
}
public override void UnsubscribeAll(Type eventType)
{
GetOrCreateHandlerFactories(eventType)
.Locking(factories => factories.Clear());
}
protected async override Task PublishToEventBusAsync(Type eventType, object eventData)
{
await PublishAsync(EventNameAttribute.GetNameOrDefault(eventType), eventData);
}
protected override void AddToUnitOfWork(IUnitOfWork unitOfWork, UnitOfWorkEventRecord eventRecord)
{
unitOfWork.AddOrReplaceDistributedEvent(eventRecord);
}
protected virtual Task PublishAsync(string eventName, object eventData)
{
var body = Serializer.Serialize(eventData);
@ -292,23 +345,12 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen
await publisher.SendMessageAsync(message);
}
protected override IEnumerable<EventTypeWithEventHandlerFactories> GetHandlerFactories(Type eventType)
{
return HandlerFactories
.Where(hf => ShouldTriggerEventForHandler(eventType, hf.Key))
.Select(handlerFactory =>
new EventTypeWithEventHandlerFactories(handlerFactory.Key, handlerFactory.Value))
.ToArray();
}
private static bool ShouldTriggerEventForHandler(Type targetEventType, Type handlerEventType)
{
return handlerEventType == targetEventType || handlerEventType.IsAssignableFrom(targetEventType);
}
protected override Task OnAddToOutboxAsync(string eventName, Type eventType, object eventData)
{
EventTypes.GetOrAdd(eventName, eventType);
if (typeof(DynamicEventData) != eventType)
{
EventTypes.GetOrAdd(eventName, eventType);
}
return base.OnAddToOutboxAsync(eventName, eventType, eventData);
}
@ -324,4 +366,83 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen
}
);
}
protected override IEnumerable<EventTypeWithEventHandlerFactories> GetHandlerFactories(Type eventType)
{
var handlerFactoryList = new List<EventTypeWithEventHandlerFactories>();
var eventNames = EventTypes.Where(x => ShouldTriggerEventForHandler(eventType, x.Value)).Select(x => x.Key).ToList();
foreach (var handlerFactory in HandlerFactories.Where(hf => ShouldTriggerEventForHandler(eventType, hf.Key)))
{
handlerFactoryList.Add(new EventTypeWithEventHandlerFactories(handlerFactory.Key, handlerFactory.Value));
}
foreach (var handlerFactory in DynamicHandlerFactories.Where(aehf => eventNames.Contains(aehf.Key)))
{
handlerFactoryList.Add(new EventTypeWithEventHandlerFactories(typeof(DynamicEventData), handlerFactory.Value));
}
return handlerFactoryList.ToArray();
}
protected override Type? GetEventTypeByEventName(string eventName)
{
return EventTypes.GetOrDefault(eventName);
}
/// <inheritdoc/>
public override void Unsubscribe(string eventName, IEventHandlerFactory factory)
{
GetOrCreateDynamicHandlerFactories(eventName)
.Locking(factories => factories.Remove(factory));
}
/// <inheritdoc/>
public override void Unsubscribe(string eventName, IEventHandler handler)
{
GetOrCreateDynamicHandlerFactories(eventName)
.Locking(factories =>
{
factories.RemoveAll(
factory =>
factory is SingleInstanceHandlerFactory singleFactory &&
singleFactory.HandlerInstance == handler
);
});
}
/// <inheritdoc/>
public override void UnsubscribeAll(string eventName)
{
GetOrCreateDynamicHandlerFactories(eventName)
.Locking(factories => factories.Clear());
}
protected override IEnumerable<EventTypeWithEventHandlerFactories> GetDynamicHandlerFactories(string eventName)
{
var eventType = GetEventTypeByEventName(eventName);
if (eventType != null)
{
return GetHandlerFactories(eventType);
}
var result = new List<EventTypeWithEventHandlerFactories>();
foreach (var handlerFactory in DynamicHandlerFactories.Where(hf => hf.Key == eventName))
{
result.Add(new EventTypeWithEventHandlerFactories(typeof(DynamicEventData), handlerFactory.Value));
}
return result;
}
private List<IEventHandlerFactory> GetOrCreateDynamicHandlerFactories(string eventName)
{
return DynamicHandlerFactories.GetOrAdd(eventName, _ => new List<IEventHandlerFactory>());
}
private static bool ShouldTriggerEventForHandler(Type targetEventType, Type handlerEventType)
{
return handlerEventType == targetEventType || handlerEventType.IsAssignableFrom(targetEventType);
}
}

114
framework/src/Volo.Abp.EventBus.Dapr/Volo/Abp/EventBus/Dapr/DaprDistributedEventBus.cs

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
@ -79,6 +79,15 @@ public class DaprDistributedEventBus : DistributedEventBusBase, ISingletonDepend
return new EventHandlerFactoryUnregistrar(this, eventType, factory);
}
/// <inheritdoc/>
public override IDisposable Subscribe(string eventName, IEventHandlerFactory handler)
{
throw new AbpException(
"Dapr distributed event bus does not support dynamic event subscriptions. " +
"Dapr requires topic subscriptions to be declared at startup and cannot add subscriptions at runtime. " +
"Use a typed event handler (IDistributedEventHandler<T>) instead.");
}
public override void Unsubscribe<TEvent>(Func<TEvent, Task> action)
{
Check.NotNull(action, nameof(action));
@ -129,37 +138,43 @@ public class DaprDistributedEventBus : DistributedEventBusBase, ISingletonDepend
GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Clear());
}
protected async override Task PublishToEventBusAsync(Type eventType, object eventData)
/// <inheritdoc/>
public override Task PublishAsync(string eventName, object eventData, bool onUnitOfWorkComplete = true)
{
await PublishToDaprAsync(eventType, eventData, null, CorrelationIdProvider.Get());
var eventType = EventTypes.GetOrDefault(eventName);
if (eventType != null)
{
var dynamicEventData = eventData as DynamicEventData ?? new DynamicEventData(eventName, eventData);
return PublishAsync(eventType, ConvertDynamicEventData(dynamicEventData.Data, eventType), onUnitOfWorkComplete);
}
throw new AbpException(
"Dapr distributed event bus does not support dynamic event publishing. " +
"Dapr requires topic subscriptions to be declared at startup. " +
"Use a typed event (PublishAsync<TEvent>) or ensure the event name matches a registered typed event.");
}
protected override void AddToUnitOfWork(IUnitOfWork unitOfWork, UnitOfWorkEventRecord eventRecord)
protected async override Task PublishToEventBusAsync(Type eventType, object eventData)
{
unitOfWork.AddOrReplaceDistributedEvent(eventRecord);
var (eventName, resolvedData) = ResolveEventForPublishing(eventType, eventData);
await PublishToDaprAsync(eventName, resolvedData, null, CorrelationIdProvider.Get());
}
protected override IEnumerable<EventTypeWithEventHandlerFactories> GetHandlerFactories(Type eventType)
protected override void AddToUnitOfWork(IUnitOfWork unitOfWork, UnitOfWorkEventRecord eventRecord)
{
var handlerFactoryList = new List<EventTypeWithEventHandlerFactories>();
foreach (var handlerFactory in HandlerFactories.Where(hf => ShouldTriggerEventForHandler(eventType, hf.Key)))
{
handlerFactoryList.Add(new EventTypeWithEventHandlerFactories(handlerFactory.Key, handlerFactory.Value));
}
return handlerFactoryList.ToArray();
unitOfWork.AddOrReplaceDistributedEvent(eventRecord);
}
public async override Task PublishFromOutboxAsync(OutgoingEventInfo outgoingEvent, OutboxConfig outboxConfig)
{
var eventType = GetEventType(outgoingEvent.EventName);
var eventType = EventTypes.GetOrDefault(outgoingEvent.EventName);
if (eventType == null)
{
return;
}
await PublishToDaprAsync(outgoingEvent.EventName, Serializer.Deserialize(outgoingEvent.EventData, eventType), outgoingEvent.Id, outgoingEvent.GetCorrelationId());
var eventData = Serializer.Deserialize(outgoingEvent.EventData, eventType);
await PublishToDaprAsync(outgoingEvent.EventName, eventData, outgoingEvent.Id, outgoingEvent.GetCorrelationId());
using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId()))
{
@ -182,7 +197,7 @@ public class DaprDistributedEventBus : DistributedEventBusBase, ISingletonDepend
public virtual async Task TriggerHandlersAsync(Type eventType, object eventData, string? messageId = null, string? correlationId = null)
{
if (await AddToInboxAsync(messageId, EventNameAttribute.GetNameOrDefault(eventType), eventType, eventData, correlationId))
if (await AddToInboxAsync(messageId, GetEventName(eventType, eventData), eventType, eventData, correlationId))
{
return;
}
@ -195,13 +210,16 @@ public class DaprDistributedEventBus : DistributedEventBusBase, ISingletonDepend
public async override Task ProcessFromInboxAsync(IncomingEventInfo incomingEvent, InboxConfig inboxConfig)
{
var eventType = GetEventType(incomingEvent.EventName);
var eventType = EventTypes.GetOrDefault(incomingEvent.EventName);
object eventData;
if (eventType == null)
{
return;
}
var eventData = Serializer.Deserialize(incomingEvent.EventData, eventType);
eventData = Serializer.Deserialize(incomingEvent.EventData, eventType);
var exceptions = new List<Exception>();
using (CorrelationIdProvider.Change(incomingEvent.GetCorrelationId()))
{
@ -218,6 +236,18 @@ public class DaprDistributedEventBus : DistributedEventBusBase, ISingletonDepend
return Serializer.Serialize(eventData);
}
protected virtual async Task PublishToDaprAsync(Type eventType, object eventData, Guid? messageId = null, string? correlationId = null)
{
await PublishToDaprAsync(EventNameAttribute.GetNameOrDefault(eventType), eventData, messageId, correlationId);
}
protected virtual async Task PublishToDaprAsync(string eventName, object eventData, Guid? messageId = null, string? correlationId = null)
{
var client = await DaprClientFactory.CreateAsync();
var data = new AbpDaprEventData(DaprEventBusOptions.PubSubName, eventName, (messageId ?? GuidGenerator.Create()).ToString("N"), Serializer.SerializeToString(eventData), correlationId);
await client.PublishEventAsync(pubsubName: DaprEventBusOptions.PubSubName, topicName: eventName, data: data);
}
protected override Task OnAddToOutboxAsync(string eventName, Type eventType, object eventData)
{
EventTypes.GetOrAdd(eventName, eventType);
@ -237,21 +267,55 @@ public class DaprDistributedEventBus : DistributedEventBusBase, ISingletonDepend
);
}
protected override IEnumerable<EventTypeWithEventHandlerFactories> GetHandlerFactories(Type eventType)
{
var handlerFactoryList = new List<EventTypeWithEventHandlerFactories>();
foreach (var handlerFactory in HandlerFactories.Where(hf => ShouldTriggerEventForHandler(eventType, hf.Key)))
{
handlerFactoryList.Add(new EventTypeWithEventHandlerFactories(handlerFactory.Key, handlerFactory.Value));
}
return handlerFactoryList.ToArray();
}
protected override Type? GetEventTypeByEventName(string eventName)
{
return EventTypes.GetOrDefault(eventName);
}
public Type? GetEventType(string eventName)
{
return EventTypes.GetOrDefault(eventName);
}
protected virtual async Task PublishToDaprAsync(Type eventType, object eventData, Guid? messageId = null, string? correlationId = null)
/// <inheritdoc/>
public override void Unsubscribe(string eventName, IEventHandlerFactory factory)
{
await PublishToDaprAsync(EventNameAttribute.GetNameOrDefault(eventType), eventData, messageId, correlationId);
throw new AbpException("Dapr distributed event bus does not support dynamic event subscriptions.");
}
protected virtual async Task PublishToDaprAsync(string eventName, object eventData, Guid? messageId = null, string? correlationId = null)
/// <inheritdoc/>
public override void Unsubscribe(string eventName, IEventHandler handler)
{
var client = await DaprClientFactory.CreateAsync();
var data = new AbpDaprEventData(DaprEventBusOptions.PubSubName, eventName, (messageId ?? GuidGenerator.Create()).ToString("N"), Serializer.SerializeToString(eventData), correlationId);
await client.PublishEventAsync(pubsubName: DaprEventBusOptions.PubSubName, topicName: eventName, data: data);
throw new AbpException("Dapr distributed event bus does not support dynamic event subscriptions.");
}
/// <inheritdoc/>
public override void UnsubscribeAll(string eventName)
{
throw new AbpException("Dapr distributed event bus does not support dynamic event subscriptions.");
}
protected override IEnumerable<EventTypeWithEventHandlerFactories> GetDynamicHandlerFactories(string eventName)
{
var eventType = GetEventTypeByEventName(eventName);
if (eventType != null)
{
return GetHandlerFactories(eventType);
}
return Array.Empty<EventTypeWithEventHandlerFactories>();
}
private static bool ShouldTriggerEventForHandler(Type targetEventType, Type handlerEventType)

143
framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
@ -29,6 +29,7 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen
protected IProducerPool ProducerPool { get; }
protected ConcurrentDictionary<Type, List<IEventHandlerFactory>> HandlerFactories { get; }
protected ConcurrentDictionary<string, Type> EventTypes { get; }
protected ConcurrentDictionary<string, List<IEventHandlerFactory>> DynamicHandlerFactories { get; }
protected IKafkaMessageConsumer Consumer { get; private set; } = default!;
public KafkaDistributedEventBus(
@ -63,6 +64,7 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen
HandlerFactories = new ConcurrentDictionary<Type, List<IEventHandlerFactory>>();
EventTypes = new ConcurrentDictionary<string, Type>();
DynamicHandlerFactories = new ConcurrentDictionary<string, List<IEventHandlerFactory>>();
}
public void Initialize()
@ -80,14 +82,24 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen
{
var eventName = message.Key;
var eventType = EventTypes.GetOrDefault(eventName);
if (eventType == null)
{
return;
}
var messageId = message.GetMessageId();
var eventData = Serializer.Deserialize(message.Value, eventType);
var correlationId = message.GetCorrelationId();
object eventData;
if (eventType != null)
{
eventData = Serializer.Deserialize(message.Value, eventType);
}
else if (DynamicHandlerFactories.ContainsKey(eventName))
{
eventData = new DynamicEventData(eventName, Serializer.Deserialize<object>(message.Value));
eventType = typeof(DynamicEventData);
}
else
{
return;
}
if (await AddToInboxAsync(messageId, eventName, eventType, eventData, correlationId))
{
@ -114,6 +126,21 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen
return new EventHandlerFactoryUnregistrar(this, eventType, factory);
}
/// <inheritdoc/>
public override IDisposable Subscribe(string eventName, IEventHandlerFactory handler)
{
var handlerFactories = GetOrCreateDynamicHandlerFactories(eventName);
if (handler.IsInFactories(handlerFactories))
{
return NullDisposable.Instance;
}
handlerFactories.Add(handler);
return new DynamicEventHandlerFactoryUnregistrar(this, eventName, handler);
}
/// <inheritdoc/>
public override void Unsubscribe<TEvent>(Func<TEvent, Task> action)
{
@ -168,6 +195,20 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen
GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Clear());
}
/// <inheritdoc/>
public override Task PublishAsync(string eventName, object eventData, bool onUnitOfWorkComplete = true)
{
var eventType = EventTypes.GetOrDefault(eventName);
var dynamicEventData = eventData as DynamicEventData ?? new DynamicEventData(eventName, eventData);
if (eventType != null)
{
return PublishAsync(eventType, ConvertDynamicEventData(dynamicEventData.Data, eventType), onUnitOfWorkComplete);
}
return PublishAsync(typeof(DynamicEventData), dynamicEventData, onUnitOfWorkComplete);
}
protected override async Task PublishToEventBusAsync(Type eventType, object eventData)
{
var headers = new Headers
@ -278,12 +319,21 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen
InboxConfig inboxConfig)
{
var eventType = EventTypes.GetOrDefault(incomingEvent.EventName);
if (eventType == null)
object eventData;
if (eventType != null)
{
eventData = Serializer.Deserialize(incomingEvent.EventData, eventType);
}
else if (DynamicHandlerFactories.ContainsKey(incomingEvent.EventName))
{
eventData = new DynamicEventData(incomingEvent.EventName, Serializer.Deserialize<object>(incomingEvent.EventData));
eventType = typeof(DynamicEventData);
}
else
{
return;
}
var eventData = Serializer.Deserialize(incomingEvent.EventData, eventType);
var exceptions = new List<Exception>();
using (CorrelationIdProvider.Change(incomingEvent.GetCorrelationId()))
{
@ -302,8 +352,8 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen
private async Task PublishAsync(string topicName, Type eventType, object eventData, Headers headers)
{
var eventName = EventNameAttribute.GetNameOrDefault(eventType);
var body = Serializer.Serialize(eventData);
var (eventName, resolvedData) = ResolveEventForPublishing(eventType, eventData);
var body = Serializer.Serialize(resolvedData);
var result = await PublishAsync(topicName, eventName, body, headers);
if (result.Status != PersistenceStatus.Persisted)
@ -332,7 +382,10 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen
protected override Task OnAddToOutboxAsync(string eventName, Type eventType, object eventData)
{
EventTypes.GetOrAdd(eventName, eventType);
if (typeof(DynamicEventData) != eventType)
{
EventTypes.GetOrAdd(eventName, eventType);
}
return base.OnAddToOutboxAsync(eventName, eventType, eventData);
}
@ -352,17 +405,75 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen
protected override IEnumerable<EventTypeWithEventHandlerFactories> GetHandlerFactories(Type eventType)
{
var handlerFactoryList = new List<EventTypeWithEventHandlerFactories>();
var eventNames = EventTypes.Where(x => ShouldTriggerEventForHandler(eventType, x.Value)).Select(x => x.Key).ToList();
foreach (var handlerFactory in HandlerFactories.Where(hf => ShouldTriggerEventForHandler(eventType, hf.Key))
)
foreach (var handlerFactory in HandlerFactories.Where(hf => ShouldTriggerEventForHandler(eventType, hf.Key)))
{
handlerFactoryList.Add(
new EventTypeWithEventHandlerFactories(handlerFactory.Key, handlerFactory.Value));
handlerFactoryList.Add(new EventTypeWithEventHandlerFactories(handlerFactory.Key, handlerFactory.Value));
}
foreach (var handlerFactory in DynamicHandlerFactories.Where(aehf => eventNames.Contains(aehf.Key)))
{
handlerFactoryList.Add(new EventTypeWithEventHandlerFactories(typeof(DynamicEventData), handlerFactory.Value));
}
return handlerFactoryList.ToArray();
}
protected override Type? GetEventTypeByEventName(string eventName)
{
return EventTypes.GetOrDefault(eventName);
}
/// <inheritdoc/>
public override void Unsubscribe(string eventName, IEventHandlerFactory factory)
{
GetOrCreateDynamicHandlerFactories(eventName).Locking(factories => factories.Remove(factory));
}
/// <inheritdoc/>
public override void Unsubscribe(string eventName, IEventHandler handler)
{
GetOrCreateDynamicHandlerFactories(eventName)
.Locking(factories =>
{
factories.RemoveAll(
factory =>
factory is SingleInstanceHandlerFactory singleFactory &&
singleFactory.HandlerInstance == handler
);
});
}
/// <inheritdoc/>
public override void UnsubscribeAll(string eventName)
{
GetOrCreateDynamicHandlerFactories(eventName).Locking(factories => factories.Clear());
}
protected override IEnumerable<EventTypeWithEventHandlerFactories> GetDynamicHandlerFactories(string eventName)
{
var eventType = GetEventTypeByEventName(eventName);
if (eventType != null)
{
return GetHandlerFactories(eventType);
}
var result = new List<EventTypeWithEventHandlerFactories>();
foreach (var handlerFactory in DynamicHandlerFactories.Where(hf => hf.Key == eventName))
{
result.Add(new EventTypeWithEventHandlerFactories(typeof(DynamicEventData), handlerFactory.Value));
}
return result;
}
private List<IEventHandlerFactory> GetOrCreateDynamicHandlerFactories(string eventName)
{
return DynamicHandlerFactories.GetOrAdd(eventName, _ => new List<IEventHandlerFactory>());
}
private static bool ShouldTriggerEventForHandler(Type targetEventType, Type handlerEventType)
{
//Should trigger same type

146
framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
@ -33,6 +33,7 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, IRabbitMqDis
//TODO: Accessing to the List<IEventHandlerFactory> may not be thread-safe!
protected ConcurrentDictionary<Type, List<IEventHandlerFactory>> HandlerFactories { get; }
protected ConcurrentDictionary<string, Type> EventTypes { get; }
protected ConcurrentDictionary<string, List<IEventHandlerFactory>> DynamicHandlerFactories { get; }
protected IRabbitMqMessageConsumerFactory MessageConsumerFactory { get; }
protected IRabbitMqMessageConsumer Consumer { get; private set; } = default!;
@ -70,6 +71,7 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, IRabbitMqDis
HandlerFactories = new ConcurrentDictionary<Type, List<IEventHandlerFactory>>();
EventTypes = new ConcurrentDictionary<string, Type>();
DynamicHandlerFactories = new ConcurrentDictionary<string, List<IEventHandlerFactory>>();
}
public virtual void Initialize()
@ -101,13 +103,23 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, IRabbitMqDis
{
var eventName = ea.RoutingKey;
var eventType = EventTypes.GetOrDefault(eventName);
if (eventType == null)
object eventData;
if (eventType != null)
{
eventData = Serializer.Deserialize(ea.Body.ToArray(), eventType);
}
else if (DynamicHandlerFactories.ContainsKey(eventName))
{
var rawBytes = ea.Body.ToArray();
eventType = typeof(DynamicEventData);
eventData = new DynamicEventData(eventName, Serializer.Deserialize<object>(rawBytes));
}
else
{
return;
}
var eventData = Serializer.Deserialize(ea.Body.ToArray(), eventType);
var correlationId = ea.BasicProperties.CorrelationId;
if (await AddToInboxAsync(ea.BasicProperties.MessageId, eventName, eventType, eventData, correlationId))
{
@ -139,6 +151,26 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, IRabbitMqDis
return new EventHandlerFactoryUnregistrar(this, eventType, factory);
}
/// <inheritdoc/>
public override IDisposable Subscribe(string eventName, IEventHandlerFactory handler)
{
var handlerFactories = GetOrCreateDynamicHandlerFactories(eventName);
if (handler.IsInFactories(handlerFactories))
{
return NullDisposable.Instance;
}
handlerFactories.Add(handler);
if (handlerFactories.Count == 1) //TODO: Multi-threading!
{
Consumer.BindAsync(eventName);
}
return new DynamicEventHandlerFactoryUnregistrar(this, eventName, handler);
}
/// <inheritdoc/>
public override void Unsubscribe<TEvent>(Func<TEvent, Task> action)
{
@ -193,6 +225,20 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, IRabbitMqDis
GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Clear());
}
/// <inheritdoc/>
public override Task PublishAsync(string eventName, object eventData, bool onUnitOfWorkComplete = true)
{
var eventType = EventTypes.GetOrDefault(eventName);
var dynamicEventData = eventData as DynamicEventData ?? new DynamicEventData(eventName, eventData);
if (eventType != null)
{
return PublishAsync(eventType, ConvertDynamicEventData(dynamicEventData.Data, eventType), onUnitOfWorkComplete);
}
return PublishAsync(typeof(DynamicEventData), dynamicEventData, onUnitOfWorkComplete);
}
protected async override Task PublishToEventBusAsync(Type eventType, object eventData)
{
await PublishAsync(eventType, eventData, correlationId: CorrelationIdProvider.Get());
@ -256,12 +302,21 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, IRabbitMqDis
InboxConfig inboxConfig)
{
var eventType = EventTypes.GetOrDefault(incomingEvent.EventName);
if (eventType == null)
object eventData;
if (eventType != null)
{
eventData = Serializer.Deserialize(incomingEvent.EventData, eventType);
}
else if (DynamicHandlerFactories.ContainsKey(incomingEvent.EventName))
{
eventData = new DynamicEventData(incomingEvent.EventName, Serializer.Deserialize<object>(incomingEvent.EventData));
eventType = typeof(DynamicEventData);
}
else
{
return;
}
var eventData = Serializer.Deserialize(incomingEvent.EventData, eventType);
var exceptions = new List<Exception>();
using (CorrelationIdProvider.Change(incomingEvent.GetCorrelationId()))
{
@ -285,8 +340,8 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, IRabbitMqDis
Guid? eventId = null,
string? correlationId = null)
{
var eventName = EventNameAttribute.GetNameOrDefault(eventType);
var body = Serializer.Serialize(eventData);
var (eventName, resolvedData) = ResolveEventForPublishing(eventType, eventData);
var body = Serializer.Serialize(resolvedData);
return PublishAsync(eventName, body, headersArguments, eventId, correlationId);
}
@ -382,7 +437,10 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, IRabbitMqDis
protected override Task OnAddToOutboxAsync(string eventName, Type eventType, object eventData)
{
EventTypes.GetOrAdd(eventName, eventType);
if (typeof(DynamicEventData) != eventType)
{
EventTypes.GetOrAdd(eventName, eventType);
}
return base.OnAddToOutboxAsync(eventName, eventType, eventData);
}
@ -398,21 +456,79 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, IRabbitMqDis
}
);
}
protected override IEnumerable<EventTypeWithEventHandlerFactories> GetHandlerFactories(Type eventType)
{
var handlerFactoryList = new List<EventTypeWithEventHandlerFactories>();
var eventNames = EventTypes.Where(x => ShouldTriggerEventForHandler(eventType, x.Value)).Select(x => x.Key).ToList();
foreach (var handlerFactory in HandlerFactories.Where(hf => ShouldTriggerEventForHandler(eventType, hf.Key)))
{
handlerFactoryList.Add(new EventTypeWithEventHandlerFactories(handlerFactory.Key, handlerFactory.Value));
}
foreach (var handlerFactory in
HandlerFactories.Where(hf => ShouldTriggerEventForHandler(eventType, hf.Key)))
foreach (var handlerFactory in DynamicHandlerFactories.Where(aehf => eventNames.Contains(aehf.Key)))
{
handlerFactoryList.Add(
new EventTypeWithEventHandlerFactories(handlerFactory.Key, handlerFactory.Value));
handlerFactoryList.Add(new EventTypeWithEventHandlerFactories(typeof(DynamicEventData), handlerFactory.Value));
}
return handlerFactoryList.ToArray();
}
protected override Type? GetEventTypeByEventName(string eventName)
{
return EventTypes.GetOrDefault(eventName);
}
/// <inheritdoc/>
public override void Unsubscribe(string eventName, IEventHandlerFactory factory)
{
GetOrCreateDynamicHandlerFactories(eventName).Locking(factories => factories.Remove(factory));
}
/// <inheritdoc/>
public override void Unsubscribe(string eventName, IEventHandler handler)
{
GetOrCreateDynamicHandlerFactories(eventName)
.Locking(factories =>
{
factories.RemoveAll(
factory =>
factory is SingleInstanceHandlerFactory singleFactory &&
singleFactory.HandlerInstance == handler
);
});
}
/// <inheritdoc/>
public override void UnsubscribeAll(string eventName)
{
GetOrCreateDynamicHandlerFactories(eventName).Locking(factories => factories.Clear());
}
protected override IEnumerable<EventTypeWithEventHandlerFactories> GetDynamicHandlerFactories(string eventName)
{
var result = new List<EventTypeWithEventHandlerFactories>();
var eventType = GetEventTypeByEventName(eventName);
if (eventType != null)
{
return GetHandlerFactories(eventType);
}
foreach (var handlerFactory in DynamicHandlerFactories.Where(hf => hf.Key == eventName))
{
result.Add(new EventTypeWithEventHandlerFactories(typeof(DynamicEventData), handlerFactory.Value));
}
return result;
}
private List<IEventHandlerFactory> GetOrCreateDynamicHandlerFactories(string eventName)
{
return DynamicHandlerFactories.GetOrAdd(eventName, _ => new List<IEventHandlerFactory>());
}
private static bool ShouldTriggerEventForHandler(Type targetEventType, Type handlerEventType)
{
//Should trigger same type

297
framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
@ -31,6 +31,7 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen
//TODO: Accessing to the List<IEventHandlerFactory> may not be thread-safe!
protected ConcurrentDictionary<Type, List<IEventHandlerFactory>> HandlerFactories { get; }
protected ConcurrentDictionary<string, Type> EventTypes { get; }
protected ConcurrentDictionary<string, List<IEventHandlerFactory>> DynamicHandlerFactories { get; }
protected AbpRebusEventBusOptions AbpRebusEventBusOptions { get; }
public RebusDistributedEventBus(
@ -63,6 +64,7 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen
HandlerFactories = new ConcurrentDictionary<Type, List<IEventHandlerFactory>>();
EventTypes = new ConcurrentDictionary<string, Type>();
DynamicHandlerFactories = new ConcurrentDictionary<string, List<IEventHandlerFactory>>();
}
public void Initialize()
@ -70,6 +72,31 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen
SubscribeHandlers(AbpDistributedEventBusOptions.Handlers);
}
public async Task ProcessEventAsync(Type eventType, object eventData)
{
var messageId = MessageContext.Current.TransportMessage.GetMessageId();
string eventName;
if (eventType == typeof(DynamicEventData) && eventData is DynamicEventData dynamicEventData)
{
eventName = dynamicEventData.EventName;
}
else
{
eventName = EventNameAttribute.GetNameOrDefault(eventType);
}
var correlationId = MessageContext.Current.Headers.GetOrDefault(EventBusConsts.CorrelationIdHeaderName);
if (await AddToInboxAsync(messageId, eventName, eventType, eventData, correlationId))
{
return;
}
using (CorrelationIdProvider.Change(correlationId))
{
await TriggerHandlersDirectAsync(eventType, eventData);
}
}
public override IDisposable Subscribe(Type eventType, IEventHandlerFactory factory)
{
var handlerFactories = GetOrCreateHandlerFactories(eventType);
@ -89,6 +116,26 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen
return new EventHandlerFactoryUnregistrar(this, eventType, factory);
}
/// <inheritdoc/>
public override IDisposable Subscribe(string eventName, IEventHandlerFactory handler)
{
var handlerFactories = GetOrCreateDynamicHandlerFactories(eventName);
if (handler.IsInFactories(handlerFactories))
{
return NullDisposable.Instance;
}
handlerFactories.Add(handler);
if (DynamicHandlerFactories.Count == 1) //TODO: Multi-threading!
{
Rebus.Subscribe(typeof(DynamicEventData));
}
return new DynamicEventHandlerFactoryUnregistrar(this, eventName, handler);
}
public override void Unsubscribe<TEvent>(Func<TEvent, Task> action)
{
Check.NotNull(action, nameof(action));
@ -143,21 +190,18 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen
Rebus.Unsubscribe(eventType);
}
public async Task ProcessEventAsync(Type eventType, object eventData)
/// <inheritdoc/>
public override Task PublishAsync(string eventName, object eventData, bool onUnitOfWorkComplete = true)
{
var messageId = MessageContext.Current.TransportMessage.GetMessageId();
var eventName = EventNameAttribute.GetNameOrDefault(eventType);
var correlationId = MessageContext.Current.Headers.GetOrDefault(EventBusConsts.CorrelationIdHeaderName);
var eventType = EventTypes.GetOrDefault(eventName);
var dynamicEventData = eventData as DynamicEventData ?? new DynamicEventData(eventName, eventData);
if (await AddToInboxAsync(messageId, eventName, eventType, eventData, correlationId))
if (eventType != null)
{
return;
return PublishAsync(eventType, ConvertDynamicEventData(dynamicEventData.Data, eventType), onUnitOfWorkComplete);
}
using (CorrelationIdProvider.Change(correlationId))
{
await TriggerHandlersDirectAsync(eventType, eventData);
}
return PublishAsync(typeof(DynamicEventData), dynamicEventData, onUnitOfWorkComplete);
}
protected async override Task PublishToEventBusAsync(Type eventType, object eventData)
@ -170,94 +214,32 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen
await PublishAsync(eventType, eventData, headersArguments: headers);
}
protected virtual async Task PublishAsync(
Type eventType,
object eventData,
Guid? eventId = null,
Dictionary<string, string>? headersArguments = null)
{
if (AbpRebusEventBusOptions.Publish != null)
{
await AbpRebusEventBusOptions.Publish(Rebus, eventType, eventData);
return;
}
headersArguments ??= new Dictionary<string, string>();
if (!headersArguments.ContainsKey(Headers.MessageId))
{
headersArguments[Headers.MessageId] = (eventId ?? GuidGenerator.Create()).ToString("N");
}
await Rebus.Publish(eventData, headersArguments);
}
protected override void AddToUnitOfWork(IUnitOfWork unitOfWork, UnitOfWorkEventRecord eventRecord)
{
unitOfWork.AddOrReplaceDistributedEvent(eventRecord);
}
protected override Task OnAddToOutboxAsync(string eventName, Type eventType, object eventData)
{
EventTypes.GetOrAdd(eventName, eventType);
return base.OnAddToOutboxAsync(eventName, eventType, eventData);
}
private List<IEventHandlerFactory> GetOrCreateHandlerFactories(Type eventType)
{
return HandlerFactories.GetOrAdd(
eventType,
type =>
{
var eventName = EventNameAttribute.GetNameOrDefault(type);
EventTypes.GetOrAdd(eventName, eventType);
return new List<IEventHandlerFactory>();
}
);
}
protected override IEnumerable<EventTypeWithEventHandlerFactories> GetHandlerFactories(Type eventType)
public async override Task PublishFromOutboxAsync(
OutgoingEventInfo outgoingEvent,
OutboxConfig outboxConfig)
{
var handlerFactoryList = new List<EventTypeWithEventHandlerFactories>();
var eventType = EventTypes.GetOrDefault(outgoingEvent.EventName);
object eventData;
foreach (var handlerFactory in HandlerFactories.Where(hf => ShouldTriggerEventForHandler(eventType, hf.Key))
)
if (eventType != null)
{
handlerFactoryList.Add(
new EventTypeWithEventHandlerFactories(handlerFactory.Key, handlerFactory.Value));
eventData = Serializer.Deserialize(outgoingEvent.EventData, eventType);
}
return handlerFactoryList.ToArray();
}
private static bool ShouldTriggerEventForHandler(Type targetEventType, Type handlerEventType)
{
//Should trigger same type
if (handlerEventType == targetEventType)
else if (DynamicHandlerFactories.ContainsKey(outgoingEvent.EventName))
{
return true;
eventData = new DynamicEventData(outgoingEvent.EventName, Serializer.Deserialize(outgoingEvent.EventData, typeof(object)));
eventType = typeof(DynamicEventData);
}
//Should trigger for inherited types
if (handlerEventType.IsAssignableFrom(targetEventType))
{
return true;
}
return false;
}
public async override Task PublishFromOutboxAsync(
OutgoingEventInfo outgoingEvent,
OutboxConfig outboxConfig)
{
var eventType = EventTypes.GetOrDefault(outgoingEvent.EventName);
if (eventType == null)
else
{
return;
}
var eventData = Serializer.Deserialize(outgoingEvent.EventData, eventType);
var headers = new Dictionary<string, string>();
if (outgoingEvent.GetCorrelationId() != null)
{
@ -306,12 +288,21 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen
InboxConfig inboxConfig)
{
var eventType = EventTypes.GetOrDefault(incomingEvent.EventName);
if (eventType == null)
object eventData;
if (eventType != null)
{
eventData = Serializer.Deserialize(incomingEvent.EventData, eventType);
}
else if (DynamicHandlerFactories.ContainsKey(incomingEvent.EventName))
{
eventData = new DynamicEventData(incomingEvent.EventName, Serializer.Deserialize(incomingEvent.EventData, typeof(object)));
eventType = typeof(DynamicEventData);
}
else
{
return;
}
var eventData = Serializer.Deserialize(incomingEvent.EventData, eventType);
var exceptions = new List<Exception>();
using (CorrelationIdProvider.Change(incomingEvent.GetCorrelationId()))
{
@ -327,4 +318,136 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen
{
return Serializer.Serialize(eventData);
}
protected virtual async Task PublishAsync(
Type eventType,
object eventData,
Guid? eventId = null,
Dictionary<string, string>? headersArguments = null)
{
if (AbpRebusEventBusOptions.Publish != null)
{
await AbpRebusEventBusOptions.Publish(Rebus, eventType, eventData);
return;
}
headersArguments ??= new Dictionary<string, string>();
if (!headersArguments.ContainsKey(Headers.MessageId))
{
headersArguments[Headers.MessageId] = (eventId ?? GuidGenerator.Create()).ToString("N");
}
await Rebus.Publish(eventData, headersArguments);
}
protected override Task OnAddToOutboxAsync(string eventName, Type eventType, object eventData)
{
if (typeof(DynamicEventData) != eventType)
{
EventTypes.GetOrAdd(eventName, eventType);
}
return base.OnAddToOutboxAsync(eventName, eventType, eventData);
}
private List<IEventHandlerFactory> GetOrCreateHandlerFactories(Type eventType)
{
return HandlerFactories.GetOrAdd(
eventType,
type =>
{
var eventName = EventNameAttribute.GetNameOrDefault(type);
EventTypes.GetOrAdd(eventName, eventType);
return new List<IEventHandlerFactory>();
}
);
}
protected override IEnumerable<EventTypeWithEventHandlerFactories> GetHandlerFactories(Type eventType)
{
var handlerFactoryList = new List<EventTypeWithEventHandlerFactories>();
var eventNames = EventTypes.Where(x => ShouldTriggerEventForHandler(eventType, x.Value)).Select(x => x.Key).ToList();
foreach (var handlerFactory in HandlerFactories.Where(hf => ShouldTriggerEventForHandler(eventType, hf.Key)))
{
handlerFactoryList.Add(new EventTypeWithEventHandlerFactories(handlerFactory.Key, handlerFactory.Value));
}
foreach (var handlerFactory in DynamicHandlerFactories.Where(aehf => eventNames.Contains(aehf.Key)))
{
handlerFactoryList.Add(new EventTypeWithEventHandlerFactories(typeof(DynamicEventData), handlerFactory.Value));
}
return handlerFactoryList.ToArray();
}
protected override Type? GetEventTypeByEventName(string eventName)
{
return EventTypes.GetOrDefault(eventName);
}
/// <inheritdoc/>
public override void Unsubscribe(string eventName, IEventHandlerFactory factory)
{
GetOrCreateDynamicHandlerFactories(eventName).Locking(factories => factories.Remove(factory));
}
/// <inheritdoc/>
public override void Unsubscribe(string eventName, IEventHandler handler)
{
GetOrCreateDynamicHandlerFactories(eventName)
.Locking(factories =>
{
factories.RemoveAll(
factory =>
factory is SingleInstanceHandlerFactory singleFactory &&
singleFactory.HandlerInstance == handler
);
});
}
/// <inheritdoc/>
public override void UnsubscribeAll(string eventName)
{
GetOrCreateDynamicHandlerFactories(eventName).Locking(factories => factories.Clear());
}
protected override IEnumerable<EventTypeWithEventHandlerFactories> GetDynamicHandlerFactories(string eventName)
{
var eventType = GetEventTypeByEventName(eventName);
if (eventType != null)
{
return GetHandlerFactories(eventType);
}
var result = new List<EventTypeWithEventHandlerFactories>();
foreach (var handlerFactory in DynamicHandlerFactories.Where(hf => hf.Key == eventName))
{
result.Add(new EventTypeWithEventHandlerFactories(typeof(DynamicEventData), handlerFactory.Value));
}
return result;
}
private List<IEventHandlerFactory> GetOrCreateDynamicHandlerFactories(string eventName)
{
return DynamicHandlerFactories.GetOrAdd(eventName, _ => new List<IEventHandlerFactory>());
}
private static bool ShouldTriggerEventForHandler(Type targetEventType, Type handlerEventType)
{
//Should trigger same type
if (handlerEventType == targetEventType)
{
return true;
}
//Should trigger for inherited types
if (handlerEventType.IsAssignableFrom(targetEventType))
{
return true;
}
return false;
}
}

71
framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs

@ -43,16 +43,25 @@ public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventB
CorrelationIdProvider = correlationIdProvider;
}
/// <inheritdoc/>
public virtual IDisposable Subscribe<TEvent>(IDistributedEventHandler<TEvent> handler) where TEvent : class
{
return Subscribe(typeof(TEvent), handler);
}
/// <inheritdoc/>
public virtual IDisposable Subscribe(string eventName, IDistributedEventHandler<DynamicEventData> handler)
{
return Subscribe(eventName, (IEventHandler)handler);
}
/// <inheritdoc/>
public override Task PublishAsync(Type eventType, object eventData, bool onUnitOfWorkComplete = true)
{
return PublishAsync(eventType, eventData, onUnitOfWorkComplete, useOutbox: true);
}
/// <inheritdoc/>
public virtual Task PublishAsync<TEvent>(
TEvent eventData,
bool onUnitOfWorkComplete = true,
@ -62,6 +71,7 @@ public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventB
return PublishAsync(typeof(TEvent), eventData, onUnitOfWorkComplete, useOutbox);
}
/// <inheritdoc/>
public virtual async Task PublishAsync(
Type eventType,
object eventData,
@ -90,11 +100,29 @@ public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventB
await TriggerDistributedEventSentAsync(new DistributedEventSent()
{
Source = DistributedEventSource.Direct,
EventName = EventNameAttribute.GetNameOrDefault(eventType),
EventData = eventData
EventName = GetEventName(eventType, eventData),
EventData = GetEventData(eventData)
});
}
/// <inheritdoc/>
public virtual Task PublishAsync(
string eventName,
object eventData,
bool onUnitOfWorkComplete = true,
bool useOutbox = true)
{
var eventType = GetEventTypeByEventName(eventName);
var dynamicEventData = eventData as DynamicEventData ?? new DynamicEventData(eventName, eventData);
if (eventType != null)
{
return PublishAsync(eventType, ConvertDynamicEventData(dynamicEventData.Data, eventType), onUnitOfWorkComplete, useOutbox);
}
return PublishAsync(typeof(DynamicEventData), dynamicEventData, onUnitOfWorkComplete, useOutbox);
}
public abstract Task PublishFromOutboxAsync(
OutgoingEventInfo outgoingEvent,
OutboxConfig outboxConfig
@ -124,7 +152,7 @@ public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventB
if (outboxConfig.Selector == null || outboxConfig.Selector(eventType))
{
var eventOutbox = (IEventOutbox)unitOfWork.ServiceProvider.GetRequiredService(outboxConfig.ImplementationType);
var eventName = EventNameAttribute.GetNameOrDefault(eventType);
(var eventName, eventData) = ResolveEventForPublishing(eventType, eventData);
await OnAddToOutboxAsync(eventName, eventType, eventData);
@ -181,12 +209,12 @@ public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventB
{
if (await eventInbox.ExistsByMessageIdAsync(messageId!))
{
// Message already exists in the inbox, no need to add again.
// This can happen in case of retries from the sender side.
addToInbox = true;
continue;
}
}
eventData = GetEventData(eventData);
var incomingEventInfo = new IncomingEventInfo(
GuidGenerator.Create(),
@ -212,8 +240,8 @@ public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventB
await TriggerDistributedEventReceivedAsync(new DistributedEventReceived
{
Source = DistributedEventSource.Direct,
EventName = EventNameAttribute.GetNameOrDefault(eventType),
EventData = eventData
EventName = GetEventName(eventType, eventData),
EventData = GetEventData(eventData)
});
await TriggerHandlersAsync(eventType, eventData);
@ -224,8 +252,8 @@ public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventB
await TriggerDistributedEventReceivedAsync(new DistributedEventReceived
{
Source = DistributedEventSource.Inbox,
EventName = EventNameAttribute.GetNameOrDefault(eventType),
EventData = eventData
EventName = GetEventName(eventType, eventData),
EventData = GetEventData(eventData)
});
await TriggerHandlersAsync(eventType, eventData, exceptions, inboxConfig);
@ -254,4 +282,29 @@ public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventB
// ignored
}
}
protected virtual string GetEventName(Type eventType, object eventData)
{
if (eventData is DynamicEventData dynamicEventData)
{
return dynamicEventData.EventName;
}
return EventNameAttribute.GetNameOrDefault(eventType);
}
protected virtual object GetEventData(object eventData)
{
if (eventData is DynamicEventData dynamicEventData)
{
return dynamicEventData.Data;
}
return eventData;
}
protected virtual (string EventName, object EventData) ResolveEventForPublishing(Type eventType, object eventData)
{
return (GetEventName(eventType, eventData), GetEventData(eventData));
}
}

122
framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus.cs

@ -3,9 +3,7 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Text.Unicode;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
@ -26,6 +24,8 @@ public class LocalDistributedEventBus : DistributedEventBusBase, ISingletonDepen
{
protected ConcurrentDictionary<string, Type> EventTypes { get; }
protected ConcurrentDictionary<string, bool> DynamicEventNames { get; }
public LocalDistributedEventBus(
IServiceScopeFactory serviceScopeFactory,
ICurrentTenant currentTenant,
@ -47,6 +47,7 @@ public class LocalDistributedEventBus : DistributedEventBusBase, ISingletonDepen
correlationIdProvider)
{
EventTypes = new ConcurrentDictionary<string, Type>();
DynamicEventNames = new ConcurrentDictionary<string, bool>();
Subscribe(abpDistributedEventBusOptions.Value.Handlers);
}
@ -71,6 +72,14 @@ public class LocalDistributedEventBus : DistributedEventBusBase, ISingletonDepen
}
}
/// <inheritdoc/>
public override IDisposable Subscribe(string eventName, IEventHandlerFactory handler)
{
DynamicEventNames.GetOrAdd(eventName, true);
return LocalEventBus.Subscribe(eventName, handler);
}
/// <inheritdoc/>
public override IDisposable Subscribe(Type eventType, IEventHandlerFactory factory)
{
var eventName = EventNameAttribute.GetNameOrDefault(eventType);
@ -93,11 +102,31 @@ public class LocalDistributedEventBus : DistributedEventBusBase, ISingletonDepen
LocalEventBus.Unsubscribe(eventType, factory);
}
/// <inheritdoc/>
public override void Unsubscribe(string eventName, IEventHandlerFactory factory)
{
LocalEventBus.Unsubscribe(eventName, factory);
}
/// <inheritdoc/>
public override void Unsubscribe(string eventName, IEventHandler handler)
{
LocalEventBus.Unsubscribe(eventName, handler);
}
/// <inheritdoc/>
public override void UnsubscribeAll(Type eventType)
{
LocalEventBus.UnsubscribeAll(eventType);
}
/// <inheritdoc/>
public override void UnsubscribeAll(string eventName)
{
LocalEventBus.UnsubscribeAll(eventName);
}
/// <inheritdoc/>
public async override Task PublishAsync(Type eventType, object eventData, bool onUnitOfWorkComplete = true, bool useOutbox = true)
{
if (onUnitOfWorkComplete && UnitOfWorkManager.Current != null)
@ -120,23 +149,43 @@ public class LocalDistributedEventBus : DistributedEventBusBase, ISingletonDepen
await TriggerDistributedEventSentAsync(new DistributedEventSent()
{
Source = DistributedEventSource.Direct,
EventName = EventNameAttribute.GetNameOrDefault(eventType),
EventData = eventData
EventName = GetEventName(eventType, eventData),
EventData = GetEventData(eventData)
});
await TriggerDistributedEventReceivedAsync(new DistributedEventReceived
{
Source = DistributedEventSource.Direct,
EventName = EventNameAttribute.GetNameOrDefault(eventType),
EventData = eventData
EventName = GetEventName(eventType, eventData),
EventData = GetEventData(eventData)
});
await PublishToEventBusAsync(eventType, eventData);
}
/// <inheritdoc/>
public override Task PublishAsync(string eventName, object eventData, bool onUnitOfWorkComplete = true)
{
return PublishAsync(eventName, eventData, onUnitOfWorkComplete, useOutbox: true);
}
/// <inheritdoc/>
public override Task PublishAsync(string eventName, object eventData, bool onUnitOfWorkComplete = true, bool useOutbox = true)
{
var eventType = EventTypes.GetOrDefault(eventName);
var dynamicEventData = eventData as DynamicEventData ?? new DynamicEventData(eventName, eventData);
if (eventType != null)
{
return PublishAsync(eventType, ConvertDynamicEventData(dynamicEventData.Data, eventType), onUnitOfWorkComplete, useOutbox);
}
return PublishAsync(typeof(DynamicEventData), dynamicEventData, onUnitOfWorkComplete, useOutbox);
}
protected async override Task PublishToEventBusAsync(Type eventType, object eventData)
{
if (await AddToInboxAsync(Guid.NewGuid().ToString(), EventNameAttribute.GetNameOrDefault(eventType), eventType, eventData, null))
if (await AddToInboxAsync(Guid.NewGuid().ToString(), GetEventName(eventType, eventData), eventType, eventData, null))
{
return;
}
@ -168,10 +217,27 @@ public class LocalDistributedEventBus : DistributedEventBusBase, ISingletonDepen
var eventType = EventTypes.GetOrDefault(outgoingEvent.EventName);
if (eventType == null)
{
return;
var isDynamic = DynamicEventNames.ContainsKey(outgoingEvent.EventName);
if (!isDynamic)
{
return;
}
eventType = typeof(DynamicEventData);
}
object eventData;
if (eventType == typeof(DynamicEventData))
{
eventData = new DynamicEventData(
outgoingEvent.EventName,
System.Text.Json.JsonSerializer.Deserialize<object>(outgoingEvent.EventData)!);
}
else
{
eventData = System.Text.Json.JsonSerializer.Deserialize(outgoingEvent.EventData, eventType)!;
}
var eventData = JsonSerializer.Deserialize(Encoding.UTF8.GetString(outgoingEvent.EventData), eventType)!;
if (await AddToInboxAsync(Guid.NewGuid().ToString(), outgoingEvent.EventName, eventType, eventData, null))
{
return;
@ -193,10 +259,27 @@ public class LocalDistributedEventBus : DistributedEventBusBase, ISingletonDepen
var eventType = EventTypes.GetOrDefault(incomingEvent.EventName);
if (eventType == null)
{
return;
var isDynamic = DynamicEventNames.ContainsKey(incomingEvent.EventName);
if (!isDynamic)
{
return;
}
eventType = typeof(DynamicEventData);
}
object eventData;
if (eventType == typeof(DynamicEventData))
{
eventData = new DynamicEventData(
incomingEvent.EventName,
System.Text.Json.JsonSerializer.Deserialize<object>(incomingEvent.EventData)!);
}
else
{
eventData = System.Text.Json.JsonSerializer.Deserialize(incomingEvent.EventData, eventType)!;
}
var eventData = JsonSerializer.Deserialize(incomingEvent.EventData, eventType);
var exceptions = new List<Exception>();
using (CorrelationIdProvider.Change(incomingEvent.GetCorrelationId()))
{
@ -210,12 +293,15 @@ public class LocalDistributedEventBus : DistributedEventBusBase, ISingletonDepen
protected override byte[] Serialize(object eventData)
{
return Encoding.UTF8.GetBytes(JsonSerializer.Serialize(eventData));
return System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(eventData);
}
protected override Task OnAddToOutboxAsync(string eventName, Type eventType, object eventData)
{
EventTypes.GetOrAdd(eventName, eventType);
if (eventType != typeof(DynamicEventData))
{
EventTypes.GetOrAdd(eventName, eventType);
}
return base.OnAddToOutboxAsync(eventName, eventType, eventData);
}
@ -223,4 +309,14 @@ public class LocalDistributedEventBus : DistributedEventBusBase, ISingletonDepen
{
return LocalEventBus.GetEventHandlerFactories(eventType);
}
protected override IEnumerable<EventTypeWithEventHandlerFactories> GetDynamicHandlerFactories(string eventName)
{
return LocalEventBus.GetDynamicEventHandlerFactories(eventName);
}
protected override Type? GetEventTypeByEventName(string eventName)
{
return EventTypes.GetOrDefault(eventName);
}
}

46
framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/NullDistributedEventBus.cs

@ -1,4 +1,4 @@
using System;
using System;
using System.Threading.Tasks;
namespace Volo.Abp.EventBus.Distributed;
@ -12,6 +12,12 @@ public sealed class NullDistributedEventBus : IDistributedEventBus
}
/// <inheritdoc/>
public Task PublishAsync(string eventName, object eventData, bool onUnitOfWorkComplete = true)
{
return Task.CompletedTask;
}
public IDisposable Subscribe<TEvent>(Func<TEvent, Task> action) where TEvent : class
{
return NullDisposable.Instance;
@ -32,6 +38,24 @@ public sealed class NullDistributedEventBus : IDistributedEventBus
return NullDisposable.Instance;
}
/// <inheritdoc/>
public IDisposable Subscribe(string eventName, IEventHandler handler)
{
return NullDisposable.Instance;
}
/// <inheritdoc/>
public IDisposable Subscribe(string eventName, IEventHandlerFactory handler)
{
return NullDisposable.Instance;
}
/// <inheritdoc/>
public IDisposable Subscribe(string eventName, IDistributedEventHandler<DynamicEventData> handler)
{
return NullDisposable.Instance;
}
public IDisposable Subscribe<TEvent>(IEventHandlerFactory factory) where TEvent : class
{
return NullDisposable.Instance;
@ -67,6 +91,16 @@ public sealed class NullDistributedEventBus : IDistributedEventBus
}
/// <inheritdoc/>
public void Unsubscribe(string eventName, IEventHandlerFactory factory)
{
}
/// <inheritdoc/>
public void Unsubscribe(string eventName, IEventHandler handler)
{
}
public void UnsubscribeAll<TEvent>() where TEvent : class
{
@ -74,7 +108,11 @@ public sealed class NullDistributedEventBus : IDistributedEventBus
public void UnsubscribeAll(Type eventType)
{
}
/// <inheritdoc/>
public void UnsubscribeAll(string eventName)
{
}
public Task PublishAsync<TEvent>(TEvent eventData, bool onUnitOfWorkComplete = true) where TEvent : class
@ -96,4 +134,10 @@ public sealed class NullDistributedEventBus : IDistributedEventBus
{
return Task.CompletedTask;
}
/// <inheritdoc/>
public Task PublishAsync(string eventName, object eventData, bool onUnitOfWorkComplete = true, bool useOutbox = true)
{
return Task.CompletedTask;
}
}

94
framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs

@ -9,6 +9,7 @@ using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Collections;
using Volo.Abp.DynamicProxy;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Json;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Reflection;
using Volo.Abp.Uow;
@ -57,6 +58,15 @@ public abstract class EventBusBase : IEventBus
return Subscribe(eventType, new SingleInstanceHandlerFactory(handler));
}
/// <inheritdoc/>
public virtual IDisposable Subscribe(string eventName, IEventHandler handler)
{
return Subscribe(eventName, new SingleInstanceHandlerFactory(handler));
}
/// <inheritdoc/>
public abstract IDisposable Subscribe(string eventName, IEventHandlerFactory handler);
/// <inheritdoc/>
public virtual IDisposable Subscribe<TEvent>(IEventHandlerFactory factory) where TEvent : class
{
@ -83,6 +93,12 @@ public abstract class EventBusBase : IEventBus
public abstract void Unsubscribe(Type eventType, IEventHandlerFactory factory);
/// <inheritdoc/>
public abstract void Unsubscribe(string eventName, IEventHandlerFactory factory);
/// <inheritdoc/>
public abstract void Unsubscribe(string eventName, IEventHandler handler);
/// <inheritdoc/>
public virtual void UnsubscribeAll<TEvent>() where TEvent : class
{
@ -92,6 +108,9 @@ public abstract class EventBusBase : IEventBus
/// <inheritdoc/>
public abstract void UnsubscribeAll(Type eventType);
/// <inheritdoc/>
public abstract void UnsubscribeAll(string eventName);
/// <inheritdoc/>
public Task PublishAsync<TEvent>(TEvent eventData, bool onUnitOfWorkComplete = true)
where TEvent : class
@ -117,6 +136,9 @@ public abstract class EventBusBase : IEventBus
await PublishToEventBusAsync(eventType, eventData);
}
/// <inheritdoc/>
public abstract Task PublishAsync(string eventName, object eventData, bool onUnitOfWorkComplete = true);
protected abstract Task PublishToEventBusAsync(Type eventType, object eventData);
protected abstract void AddToUnitOfWork(IUnitOfWork unitOfWork, UnitOfWorkEventRecord eventRecord);
@ -137,31 +159,81 @@ public abstract class EventBusBase : IEventBus
{
await new SynchronizationContextRemover();
foreach (var handlerFactories in GetHandlerFactories(eventType).ToList())
var (handlerFactoriesList, actualEventType) = ResolveHandlerFactories(eventType, eventData);
foreach (var handlerFactories in handlerFactoriesList)
{
foreach (var handlerFactory in handlerFactories.EventHandlerFactories.ToList())
{
await TriggerHandlerAsync(handlerFactory, handlerFactories.EventType, eventData, exceptions, inboxConfig);
var resolvedEventData = ResolveEventDataForHandler(eventData, eventType, handlerFactories.EventType);
await TriggerHandlerAsync(handlerFactory, handlerFactories.EventType, resolvedEventData, exceptions, inboxConfig);
}
}
//Implements generic argument inheritance. See IEventDataWithInheritableGenericArgument
if (eventType.GetTypeInfo().IsGenericType &&
eventType.GetGenericArguments().Length == 1 &&
typeof(IEventDataWithInheritableGenericArgument).IsAssignableFrom(eventType))
if (actualEventType != null &&
actualEventType.GetTypeInfo().IsGenericType &&
actualEventType.GetGenericArguments().Length == 1 &&
typeof(IEventDataWithInheritableGenericArgument).IsAssignableFrom(actualEventType))
{
var genericArg = eventType.GetGenericArguments()[0];
var resolvedEventData = eventData is DynamicEventData aed
? ConvertDynamicEventData(aed.Data, actualEventType)
: eventData;
var genericArg = actualEventType.GetGenericArguments()[0];
var baseArg = genericArg.GetTypeInfo().BaseType;
if (baseArg != null)
{
var baseEventType = eventType.GetGenericTypeDefinition().MakeGenericType(baseArg);
var constructorArgs = ((IEventDataWithInheritableGenericArgument)eventData).GetConstructorArgs();
var baseEventType = actualEventType.GetGenericTypeDefinition().MakeGenericType(baseArg);
var constructorArgs = ((IEventDataWithInheritableGenericArgument)resolvedEventData).GetConstructorArgs();
var baseEventData = Activator.CreateInstance(baseEventType, constructorArgs)!;
await PublishToEventBusAsync(baseEventType, baseEventData);
}
}
}
protected virtual (List<EventTypeWithEventHandlerFactories> Factories, Type? ActualEventType) ResolveHandlerFactories(
Type eventType,
object eventData)
{
if (eventData is DynamicEventData dynamicEventData)
{
return (
GetDynamicHandlerFactories(dynamicEventData.EventName).ToList(),
GetEventTypeByEventName(dynamicEventData.EventName)
);
}
return (GetHandlerFactories(eventType).ToList(), eventType);
}
protected virtual object ResolveEventDataForHandler(object eventData, Type sourceEventType, Type handlerEventType)
{
if (eventData is DynamicEventData dynamicEventData && handlerEventType != typeof(DynamicEventData))
{
return ConvertDynamicEventData(dynamicEventData.Data, handlerEventType);
}
if (handlerEventType == typeof(DynamicEventData) && eventData is not DynamicEventData)
{
return new DynamicEventData(EventNameAttribute.GetNameOrDefault(sourceEventType), eventData);
}
return eventData;
}
protected virtual object ConvertDynamicEventData(object data, Type targetType)
{
if (targetType.IsInstanceOfType(data))
{
return data;
}
using var scope = ServiceScopeFactory.CreateScope();
var jsonSerializer = scope.ServiceProvider.GetRequiredService<IJsonSerializer>();
var json = jsonSerializer.Serialize(data);
return jsonSerializer.Deserialize(targetType, json);
}
protected void ThrowOriginalExceptions(Type eventType, List<Exception> exceptions)
{
if (exceptions.Count == 1)
@ -198,6 +270,10 @@ public abstract class EventBusBase : IEventBus
protected abstract IEnumerable<EventTypeWithEventHandlerFactories> GetHandlerFactories(Type eventType);
protected abstract IEnumerable<EventTypeWithEventHandlerFactories> GetDynamicHandlerFactories(string eventName);
protected abstract Type? GetEventTypeByEventName(string eventName);
protected virtual async Task TriggerHandlerAsync(IEventHandlerFactory asyncHandlerFactory, Type eventType,
object eventData, List<Exception> exceptions, InboxConfig? inboxConfig = null)
{

22
framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventHandlerFactoryUnregistrar.cs

@ -23,3 +23,25 @@ public class EventHandlerFactoryUnregistrar : IDisposable
_eventBus.Unsubscribe(_eventType, _factory);
}
}
/// <summary>
/// Used to unregister an <see cref="IEventHandlerFactory"/> for a string-based event name on <see cref="IDisposable.Dispose"/> method.
/// </summary>
public class DynamicEventHandlerFactoryUnregistrar : IDisposable
{
private readonly IEventBus _eventBus;
private readonly string _eventName;
private readonly IEventHandlerFactory _factory;
public DynamicEventHandlerFactoryUnregistrar(IEventBus eventBus, string eventName, IEventHandlerFactory factory)
{
_eventBus = eventBus;
_eventName = eventName;
_factory = factory;
}
public void Dispose()
{
_eventBus.Unsubscribe(_eventName, _factory);
}
}

123
framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventBus.cs

@ -30,6 +30,10 @@ public class LocalEventBus : EventBusBase, ILocalEventBus, ISingletonDependency
protected ConcurrentDictionary<Type, List<IEventHandlerFactory>> HandlerFactories { get; }
protected ConcurrentDictionary<string, Type> EventTypes { get; }
protected ConcurrentDictionary<string, List<IEventHandlerFactory>> DynamicEventHandlerFactories { get; }
public LocalEventBus(
IOptions<AbpLocalEventBusOptions> options,
IServiceScopeFactory serviceScopeFactory,
@ -42,6 +46,8 @@ public class LocalEventBus : EventBusBase, ILocalEventBus, ISingletonDependency
Logger = NullLogger<LocalEventBus>.Instance;
HandlerFactories = new ConcurrentDictionary<Type, List<IEventHandlerFactory>>();
EventTypes = new ConcurrentDictionary<string, Type>();
DynamicEventHandlerFactories = new ConcurrentDictionary<string, List<IEventHandlerFactory>>();
SubscribeHandlers(Options.Handlers);
}
@ -51,9 +57,25 @@ public class LocalEventBus : EventBusBase, ILocalEventBus, ISingletonDependency
return Subscribe(typeof(TEvent), handler);
}
/// <inheritdoc/>
public override IDisposable Subscribe(string eventName, IEventHandlerFactory handler)
{
GetOrCreateDynamicHandlerFactories(eventName).Locking(factories =>
{
if (!handler.IsInFactories(factories))
{
factories.Add(handler);
}
});
return new DynamicEventHandlerFactoryUnregistrar(this, eventName, handler);
}
/// <inheritdoc/>
public override IDisposable Subscribe(Type eventType, IEventHandlerFactory factory)
{
EventTypes.GetOrAdd(EventNameAttribute.GetNameOrDefault(eventType), eventType);
GetOrCreateHandlerFactories(eventType)
.Locking(factories =>
{
@ -115,12 +137,53 @@ public class LocalEventBus : EventBusBase, ILocalEventBus, ISingletonDependency
GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Remove(factory));
}
/// <inheritdoc/>
public override void Unsubscribe(string eventName, IEventHandlerFactory factory)
{
GetOrCreateDynamicHandlerFactories(eventName).Locking(factories => factories.Remove(factory));
}
/// <inheritdoc/>
public override void Unsubscribe(string eventName, IEventHandler handler)
{
GetOrCreateDynamicHandlerFactories(eventName)
.Locking(factories =>
{
factories.RemoveAll(
factory =>
factory is SingleInstanceHandlerFactory singleFactory &&
singleFactory.HandlerInstance == handler
);
});
}
/// <inheritdoc/>
public override void UnsubscribeAll(Type eventType)
{
GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Clear());
}
/// <inheritdoc/>
public override void UnsubscribeAll(string eventName)
{
GetOrCreateDynamicHandlerFactories(eventName).Locking(factories => factories.Clear());
}
/// <inheritdoc/>
public override Task PublishAsync(string eventName, object eventData, bool onUnitOfWorkComplete = true)
{
var eventType = EventTypes.GetOrDefault(eventName);
var dynamicEventData = eventData as DynamicEventData ?? new DynamicEventData(eventName, eventData);
if (eventType != null)
{
return PublishAsync(eventType, ConvertDynamicEventData(dynamicEventData.Data, eventType), onUnitOfWorkComplete);
}
return PublishAsync(typeof(DynamicEventData), dynamicEventData, onUnitOfWorkComplete);
}
protected override async Task PublishToEventBusAsync(Type eventType, object eventData)
{
await PublishAsync(new LocalEventMessage(Guid.NewGuid(), eventData, eventType));
@ -141,9 +204,17 @@ public class LocalEventBus : EventBusBase, ILocalEventBus, ISingletonDependency
return GetHandlerFactories(eventType).ToList();
}
/// <inheritdoc/>
public virtual List<EventTypeWithEventHandlerFactories> GetDynamicEventHandlerFactories(string eventName)
{
return GetDynamicHandlerFactories(eventName).ToList();
}
protected override IEnumerable<EventTypeWithEventHandlerFactories> GetHandlerFactories(Type eventType)
{
var handlerFactoryList = new List<Tuple<IEventHandlerFactory, Type, int>>();
var eventNames = EventTypes.Where(x => ShouldTriggerEventForHandler(eventType, x.Value)).Select(x => x.Key).ToList();
foreach (var handlerFactory in HandlerFactories.Where(hf => ShouldTriggerEventForHandler(eventType, hf.Key)))
{
foreach (var factory in handlerFactory.Value)
@ -155,23 +226,71 @@ public class LocalEventBus : EventBusBase, ILocalEventBus, ISingletonDependency
}
}
foreach (var handlerFactory in DynamicEventHandlerFactories.Where(aehf => eventNames.Contains(aehf.Key)))
{
foreach (var factory in handlerFactory.Value)
{
handlerFactoryList.Add(new Tuple<IEventHandlerFactory, Type, int>(
factory,
typeof(DynamicEventData),
ReflectionHelper.GetAttributesOfMemberOrDeclaringType<LocalEventHandlerOrderAttribute>(factory.GetHandler().EventHandler.GetType()).FirstOrDefault()?.Order ?? 0));
}
}
return handlerFactoryList.OrderBy(x => x.Item3).Select(x => new EventTypeWithEventHandlerFactories(x.Item2, new List<IEventHandlerFactory> {x.Item1})).ToArray();
}
protected override IEnumerable<EventTypeWithEventHandlerFactories> GetDynamicHandlerFactories(string eventName)
{
var eventType = EventTypes.GetOrDefault(eventName);
if (eventType != null)
{
return GetHandlerFactories(eventType);
}
var handlerFactoryList = new List<Tuple<IEventHandlerFactory, Type, int>>();
foreach (var handlerFactory in DynamicEventHandlerFactories.Where(aehf => aehf.Key == eventName))
{
foreach (var factory in handlerFactory.Value)
{
using var handler = factory.GetHandler();
var handlerType = handler.EventHandler.GetType();
handlerFactoryList.Add(new Tuple<IEventHandlerFactory, Type, int>(
factory,
typeof(DynamicEventData),
ReflectionHelper
.GetAttributesOfMemberOrDeclaringType<LocalEventHandlerOrderAttribute>(handlerType)
.FirstOrDefault()?.Order ?? 0));
}
}
return handlerFactoryList.OrderBy(x => x.Item3).Select(x =>
new EventTypeWithEventHandlerFactories(x.Item2, new List<IEventHandlerFactory> { x.Item1 })).ToArray();
}
protected override Type? GetEventTypeByEventName(string eventName)
{
return EventTypes.GetOrDefault(eventName);
}
private List<IEventHandlerFactory> GetOrCreateHandlerFactories(Type eventType)
{
return HandlerFactories.GetOrAdd(eventType, (type) => new List<IEventHandlerFactory>());
}
private List<IEventHandlerFactory> GetOrCreateDynamicHandlerFactories(string eventName)
{
return DynamicEventHandlerFactories.GetOrAdd(eventName, (name) => new List<IEventHandlerFactory>());
}
private static bool ShouldTriggerEventForHandler(Type targetEventType, Type handlerEventType)
{
//Should trigger same type
if (handlerEventType == targetEventType)
{
return true;
}
//Should trigger for inherited types
if (handlerEventType.IsAssignableFrom(targetEventType))
{
return true;

40
framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/NullLocalEventBus.cs

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
@ -13,6 +13,12 @@ public sealed class NullLocalEventBus : ILocalEventBus
}
/// <inheritdoc/>
public Task PublishAsync(string eventName, object eventData, bool onUnitOfWorkComplete = true)
{
return Task.CompletedTask;
}
public IDisposable Subscribe<TEvent>(Func<TEvent, Task> action) where TEvent : class
{
return NullDisposable.Instance;
@ -28,6 +34,12 @@ public sealed class NullLocalEventBus : ILocalEventBus
return new List<EventTypeWithEventHandlerFactories>();
}
/// <inheritdoc/>
public List<EventTypeWithEventHandlerFactories> GetDynamicEventHandlerFactories(string eventName)
{
return new List<EventTypeWithEventHandlerFactories>();
}
public IDisposable Subscribe<TEvent, THandler>() where TEvent : class where THandler : IEventHandler, new()
{
return NullDisposable.Instance;
@ -38,6 +50,18 @@ public sealed class NullLocalEventBus : ILocalEventBus
return NullDisposable.Instance;
}
/// <inheritdoc/>
public IDisposable Subscribe(string eventName, IEventHandler handler)
{
return NullDisposable.Instance;
}
/// <inheritdoc/>
public IDisposable Subscribe(string eventName, IEventHandlerFactory handler)
{
return NullDisposable.Instance;
}
public IDisposable Subscribe<TEvent>(IEventHandlerFactory factory) where TEvent : class
{
return NullDisposable.Instance;
@ -73,6 +97,16 @@ public sealed class NullLocalEventBus : ILocalEventBus
}
/// <inheritdoc/>
public void Unsubscribe(string eventName, IEventHandlerFactory factory)
{
}
/// <inheritdoc/>
public void Unsubscribe(string eventName, IEventHandler handler)
{
}
public void UnsubscribeAll<TEvent>() where TEvent : class
{
@ -80,7 +114,11 @@ public sealed class NullLocalEventBus : ILocalEventBus
public void UnsubscribeAll(Type eventType)
{
}
/// <inheritdoc/>
public void UnsubscribeAll(string eventName)
{
}
public Task PublishAsync<TEvent>(TEvent eventData, bool onUnitOfWorkComplete = true) where TEvent : class

280
framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus_Test.cs

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Domain.Entities.Events.Distributed;
@ -10,10 +11,17 @@ namespace Volo.Abp.EventBus.Distributed;
public class LocalDistributedEventBus_Test : LocalDistributedEventBusTestBase
{
public LocalDistributedEventBus_Test()
{
MySimpleDistributedTransientEventHandler.HandleCount = 0;
MySimpleDistributedTransientEventHandler.DisposeCount = 0;
MySimpleDistributedSingleInstanceEventHandler.TenantId = null;
}
[Fact]
public async Task Should_Call_Handler_AndDispose()
{
DistributedEventBus.Subscribe<MySimpleEventData, MySimpleDistributedTransientEventHandler>();
using var subscription = DistributedEventBus.Subscribe<MySimpleEventData, MySimpleDistributedTransientEventHandler>();
await DistributedEventBus.PublishAsync(new MySimpleEventData(1));
await DistributedEventBus.PublishAsync(new MySimpleEventData(2));
@ -23,12 +31,255 @@ public class LocalDistributedEventBus_Test : LocalDistributedEventBusTestBase
Assert.Equal(3, MySimpleDistributedTransientEventHandler.DisposeCount);
}
[Fact]
public async Task Should_Handle_Typed_Handler_When_Published_With_EventName()
{
using var subscription = DistributedEventBus.Subscribe<MySimpleEventData, MySimpleDistributedTransientEventHandler>();
var eventName = EventNameAttribute.GetNameOrDefault<MySimpleEventData>();
await DistributedEventBus.PublishAsync(eventName, new MySimpleEventData(1));
await DistributedEventBus.PublishAsync(eventName, new Dictionary<string, object>()
{
{"Value", 2}
});
await DistributedEventBus.PublishAsync(eventName, new { Value = 3 });
Assert.Equal(3, MySimpleDistributedTransientEventHandler.HandleCount);
Assert.Equal(3, MySimpleDistributedTransientEventHandler.DisposeCount);
}
[Fact]
public async Task Should_Handle_Dynamic_Handler_When_Published_With_EventName()
{
var handleCount = 0;
var eventName = "MyEvent-" + Guid.NewGuid().ToString("N");
using var subscription = DistributedEventBus.Subscribe(eventName,
new SingleInstanceHandlerFactory(new ActionEventHandler<DynamicEventData>(async (d) =>
{
handleCount++;
await Task.CompletedTask;
})));
await DistributedEventBus.PublishAsync(eventName, new MySimpleEventData(1));
await DistributedEventBus.PublishAsync(eventName, new Dictionary<string, object>()
{
{"Value", 2}
});
await DistributedEventBus.PublishAsync(eventName, new { Value = 3 });
await DistributedEventBus.PublishAsync(eventName, new[] { 1, 2, 3 });
Assert.Equal(4, handleCount);
}
[Fact]
public async Task Should_Handle_Dynamic_Handler_When_Published_With_DynamicEventData()
{
var handleCount = 0;
var eventName = "MyEvent-" + Guid.NewGuid().ToString("N");
using var subscription = DistributedEventBus.Subscribe(eventName,
new SingleInstanceHandlerFactory(new ActionEventHandler<DynamicEventData>(async (d) =>
{
handleCount++;
d.Data.ShouldNotBeNull();
await Task.CompletedTask;
})));
await DistributedEventBus.PublishAsync(new DynamicEventData(eventName, new MySimpleEventData(1)));
await DistributedEventBus.PublishAsync(new DynamicEventData(eventName, new Dictionary<string, object>()
{
{"Value", 2}
}));
await DistributedEventBus.PublishAsync(new DynamicEventData(eventName, new { Value = 3 }));
Assert.Equal(3, handleCount);
}
[Fact]
public async Task Should_Handle_Typed_Handler_When_Published_With_DynamicEventData()
{
using var subscription = DistributedEventBus.Subscribe<MySimpleEventData, MySimpleDistributedTransientEventHandler>();
var eventName = EventNameAttribute.GetNameOrDefault<MySimpleEventData>();
await DistributedEventBus.PublishAsync(new DynamicEventData(eventName, new MySimpleEventData(1)));
await DistributedEventBus.PublishAsync(new DynamicEventData(eventName, new Dictionary<string, object>()
{
{"Value", 2}
}));
await DistributedEventBus.PublishAsync(new DynamicEventData(eventName, new { Value = 3 }));
Assert.Equal(3, MySimpleDistributedTransientEventHandler.HandleCount);
}
[Fact]
public async Task Should_Trigger_Both_Typed_And_Dynamic_Handlers_For_Typed_Event()
{
using var typedSubscription = DistributedEventBus.Subscribe<MySimpleEventData, MySimpleDistributedTransientEventHandler>();
var eventName = EventNameAttribute.GetNameOrDefault<MySimpleEventData>();
var dynamicHandleCount = 0;
using var dynamicSubscription = DistributedEventBus.Subscribe(eventName, new SingleInstanceHandlerFactory(new ActionEventHandler<DynamicEventData>(async (d) =>
{
dynamicHandleCount++;
await Task.CompletedTask;
})));
await DistributedEventBus.PublishAsync(new MySimpleEventData(1));
await DistributedEventBus.PublishAsync(new MySimpleEventData(2));
await DistributedEventBus.PublishAsync(new MySimpleEventData(3));
Assert.Equal(3, MySimpleDistributedTransientEventHandler.HandleCount);
Assert.Equal(3, dynamicHandleCount);
}
[Fact]
public async Task Should_Trigger_Both_Handlers_For_Mixed_Typed_And_Dynamic_Publish()
{
using var typedSubscription = DistributedEventBus.Subscribe<MySimpleEventData, MySimpleDistributedTransientEventHandler>();
var eventName = EventNameAttribute.GetNameOrDefault<MySimpleEventData>();
var dynamicHandleCount = 0;
using var dynamicSubscription = DistributedEventBus.Subscribe(eventName, new SingleInstanceHandlerFactory(new ActionEventHandler<DynamicEventData>(async (d) =>
{
dynamicHandleCount++;
await Task.CompletedTask;
})));
await DistributedEventBus.PublishAsync(new MySimpleEventData(1));
await DistributedEventBus.PublishAsync(new DynamicEventData(eventName, new Dictionary<string, object>()
{
{"Value", 2}
}));
await DistributedEventBus.PublishAsync(new DynamicEventData(eventName, new { Value = 3 }));
Assert.Equal(3, MySimpleDistributedTransientEventHandler.HandleCount);
Assert.Equal(3, dynamicHandleCount);
}
[Fact]
public async Task Should_Unsubscribe_Dynamic_Handler()
{
var handleCount = 0;
var eventName = "MyEvent-" + Guid.NewGuid().ToString("N");
var handler = new ActionEventHandler<DynamicEventData>(async (d) =>
{
handleCount++;
await Task.CompletedTask;
});
var factory = new SingleInstanceHandlerFactory(handler);
var disposable = DistributedEventBus.Subscribe(eventName, factory);
await DistributedEventBus.PublishAsync(eventName, new { Value = 1 });
Assert.Equal(1, handleCount);
disposable.Dispose();
await DistributedEventBus.PublishAsync(eventName, new { Value = 2 });
Assert.Equal(1, handleCount);
}
[Fact]
public async Task Should_Not_Throw_For_Unknown_Event_Name()
{
// Publishing to an unknown event name should not throw (consistent with typed PublishAsync behavior)
await DistributedEventBus.PublishAsync("NonExistentEvent", new { Value = 1 });
}
[Fact]
public async Task Should_Convert_DynamicEventData_To_Typed_Object()
{
MySimpleEventData? receivedData = null;
using var subscription = DistributedEventBus.Subscribe<MySimpleEventData>(async (data) =>
{
receivedData = data;
await Task.CompletedTask;
});
var eventName = EventNameAttribute.GetNameOrDefault<MySimpleEventData>();
await DistributedEventBus.PublishAsync(eventName, new { Value = 42 });
receivedData.ShouldNotBeNull();
receivedData.Value.ShouldBe(42);
}
[Fact]
public async Task Should_Subscribe_With_IDistributedEventHandler()
{
var handleCount = 0;
var eventName = "MyEvent-" + Guid.NewGuid().ToString("N");
using var subscription = DistributedEventBus.Subscribe(eventName,
new TestDynamicDistributedEventHandler(() => handleCount++));
await DistributedEventBus.PublishAsync(eventName, new { Value = 1 });
await DistributedEventBus.PublishAsync(eventName, new { Value = 2 });
Assert.Equal(2, handleCount);
}
[Fact]
public async Task Should_Handle_Multiple_Dynamic_Events_Independently()
{
var countA = 0;
var countB = 0;
var eventNameA = "EventA-" + Guid.NewGuid().ToString("N");
var eventNameB = "EventB-" + Guid.NewGuid().ToString("N");
using var subA = DistributedEventBus.Subscribe(eventNameA,
new SingleInstanceHandlerFactory(new ActionEventHandler<DynamicEventData>(async (d) =>
{
countA++;
await Task.CompletedTask;
})));
using var subB = DistributedEventBus.Subscribe(eventNameB,
new SingleInstanceHandlerFactory(new ActionEventHandler<DynamicEventData>(async (d) =>
{
countB++;
await Task.CompletedTask;
})));
await DistributedEventBus.PublishAsync(eventNameA, new { Value = 1 });
await DistributedEventBus.PublishAsync(eventNameB, new { Value = 2 });
await DistributedEventBus.PublishAsync(eventNameA, new { Value = 3 });
Assert.Equal(2, countA);
Assert.Equal(1, countB);
}
[Fact]
public async Task Should_Receive_EventName_In_DynamicEventData()
{
string? receivedEventName = null;
var eventName = "TestEvent-" + Guid.NewGuid().ToString("N");
using var subscription = DistributedEventBus.Subscribe(eventName,
new SingleInstanceHandlerFactory(new ActionEventHandler<DynamicEventData>(async (d) =>
{
receivedEventName = d.EventName;
await Task.CompletedTask;
})));
await DistributedEventBus.PublishAsync(eventName, new { Value = 1 });
receivedEventName.ShouldBe(eventName);
}
[Fact]
public async Task Should_Change_TenantId_If_EventData_Is_MultiTenant()
{
var tenantId = Guid.NewGuid();
DistributedEventBus.Subscribe<MySimpleEventData>(GetRequiredService<MySimpleDistributedSingleInstanceEventHandler>());
using var subscription = DistributedEventBus.Subscribe<MySimpleEventData>(GetRequiredService<MySimpleDistributedSingleInstanceEventHandler>());
await DistributedEventBus.PublishAsync(new MySimpleEventData(3, tenantId));
@ -40,7 +291,7 @@ public class LocalDistributedEventBus_Test : LocalDistributedEventBusTestBase
{
var tenantId = Guid.NewGuid();
DistributedEventBus.Subscribe<EntityCreatedEto<MySimpleEventData>>(GetRequiredService<MySimpleDistributedSingleInstanceEventHandler>());
using var subscription = DistributedEventBus.Subscribe<EntityCreatedEto<MySimpleEventData>>(GetRequiredService<MySimpleDistributedSingleInstanceEventHandler>());
await DistributedEventBus.PublishAsync(new MySimpleEventData(3, tenantId));
@ -52,7 +303,7 @@ public class LocalDistributedEventBus_Test : LocalDistributedEventBusTestBase
{
var tenantId = Guid.NewGuid();
DistributedEventBus.Subscribe<MySimpleEto>(GetRequiredService<MySimpleDistributedSingleInstanceEventHandler>());
using var subscription = DistributedEventBus.Subscribe<MySimpleEto>(GetRequiredService<MySimpleDistributedSingleInstanceEventHandler>());
await DistributedEventBus.PublishAsync(new MySimpleEto
{
@ -70,10 +321,10 @@ public class LocalDistributedEventBus_Test : LocalDistributedEventBusTestBase
{
var localEventBus = GetRequiredService<ILocalEventBus>();
localEventBus.Subscribe<DistributedEventSent, DistributedEventHandles>();
localEventBus.Subscribe<DistributedEventReceived, DistributedEventHandles>();
using var distributedEventSentSubscription = localEventBus.Subscribe<DistributedEventSent, DistributedEventHandles>();
using var distributedEventReceivedSubscription = localEventBus.Subscribe<DistributedEventReceived, DistributedEventHandles>();
DistributedEventBus.Subscribe<MyEventDate, MyEventHandle>();
using var subscription = DistributedEventBus.Subscribe<MyEventDate, MyEventHandle>();
using (var uow = GetRequiredService<IUnitOfWorkManager>().Begin())
{
@ -121,4 +372,19 @@ public class LocalDistributedEventBus_Test : LocalDistributedEventBusTestBase
}
}
class TestDynamicDistributedEventHandler : IDistributedEventHandler<DynamicEventData>
{
private readonly Action _onHandle;
public TestDynamicDistributedEventHandler(Action onHandle)
{
_onHandle = onHandle;
}
public Task HandleEventAsync(DynamicEventData eventData)
{
_onHandle();
return Task.CompletedTask;
}
}
}

258
framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Local/LocalEventBus_Dynamic_Test.cs

@ -0,0 +1,258 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Shouldly;
using Xunit;
namespace Volo.Abp.EventBus.Local;
public class LocalEventBus_Dynamic_Test : EventBusTestBase
{
[Fact]
public async Task Should_Handle_Dynamic_Handler_With_EventName()
{
var handleCount = 0;
var eventName = "TestEvent-" + Guid.NewGuid().ToString("N");
using var subscription = LocalEventBus.Subscribe(eventName,
new SingleInstanceHandlerFactory(new ActionEventHandler<DynamicEventData>(async (d) =>
{
handleCount++;
d.EventName.ShouldBe(eventName);
await Task.CompletedTask;
})));
await LocalEventBus.PublishAsync(eventName, new { Value = 1 });
await LocalEventBus.PublishAsync(eventName, new { Value = 2 });
handleCount.ShouldBe(2);
}
[Fact]
public async Task Should_Handle_Typed_Handler_When_Published_With_EventName()
{
var handleCount = 0;
using var subscription = LocalEventBus.Subscribe<MySimpleEventData>(async (data) =>
{
handleCount++;
await Task.CompletedTask;
});
var eventName = EventNameAttribute.GetNameOrDefault<MySimpleEventData>();
await LocalEventBus.PublishAsync(eventName, new MySimpleEventData(42));
handleCount.ShouldBe(1);
}
[Fact]
public async Task Should_Convert_Dictionary_To_Typed_Handler()
{
MySimpleEventData? receivedData = null;
using var subscription = LocalEventBus.Subscribe<MySimpleEventData>(async (data) =>
{
receivedData = data;
await Task.CompletedTask;
});
var eventName = EventNameAttribute.GetNameOrDefault<MySimpleEventData>();
await LocalEventBus.PublishAsync(eventName, new Dictionary<string, object>
{
{ "Value", 42 }
});
receivedData.ShouldNotBeNull();
receivedData.Value.ShouldBe(42);
}
[Fact]
public async Task Should_Trigger_Both_Typed_And_Dynamic_Handlers()
{
var typedHandleCount = 0;
var dynamicHandleCount = 0;
using var typedSubscription = LocalEventBus.Subscribe<MySimpleEventData>(async (data) =>
{
typedHandleCount++;
await Task.CompletedTask;
});
var eventName = EventNameAttribute.GetNameOrDefault<MySimpleEventData>();
using var dynamicSubscription = LocalEventBus.Subscribe(eventName,
new SingleInstanceHandlerFactory(new ActionEventHandler<DynamicEventData>(async (d) =>
{
dynamicHandleCount++;
await Task.CompletedTask;
})));
await LocalEventBus.PublishAsync(new MySimpleEventData(1));
typedHandleCount.ShouldBe(1);
dynamicHandleCount.ShouldBe(1);
}
[Fact]
public async Task Should_Unsubscribe_Dynamic_Handler()
{
var handleCount = 0;
var eventName = "TestEvent-" + Guid.NewGuid().ToString("N");
var handler = new ActionEventHandler<DynamicEventData>(async (d) =>
{
handleCount++;
await Task.CompletedTask;
});
var factory = new SingleInstanceHandlerFactory(handler);
var disposable = LocalEventBus.Subscribe(eventName, factory);
await LocalEventBus.PublishAsync(eventName, new { Value = 1 });
handleCount.ShouldBe(1);
disposable.Dispose();
await LocalEventBus.PublishAsync(eventName, new { Value = 2 });
handleCount.ShouldBe(1);
}
[Fact]
public async Task Should_Not_Throw_For_Unknown_Event_Name()
{
// Publishing to an unknown event name should not throw (consistent with typed PublishAsync behavior)
await LocalEventBus.PublishAsync("NonExistentEvent", new { Value = 1 });
}
[Fact]
public async Task Should_Access_Data_In_Dynamic_Handler()
{
object? receivedData = null;
var eventName = "TestEvent-" + Guid.NewGuid().ToString("N");
using var subscription = LocalEventBus.Subscribe(eventName,
new SingleInstanceHandlerFactory(new ActionEventHandler<DynamicEventData>(async (d) =>
{
receivedData = d.Data;
await Task.CompletedTask;
})));
await LocalEventBus.PublishAsync(eventName, new { Name = "Hello", Count = 42 });
receivedData.ShouldNotBeNull();
}
[Fact]
public async Task Should_Receive_Typed_Data_Via_Typed_Handler_From_Dynamic_Publish()
{
MySimpleEventData? receivedData = null;
var eventName = EventNameAttribute.GetNameOrDefault<MySimpleEventData>();
using var subscription = LocalEventBus.Subscribe<MySimpleEventData>(async (d) =>
{
receivedData = d;
await Task.CompletedTask;
});
await LocalEventBus.PublishAsync(eventName, new MySimpleEventData(99));
receivedData.ShouldNotBeNull();
receivedData.Value.ShouldBe(99);
}
[Fact]
public async Task Should_Unsubscribe_All_Dynamic_Handlers()
{
var handleCount = 0;
var eventName = "TestEvent-" + Guid.NewGuid().ToString("N");
LocalEventBus.Subscribe(eventName,
new SingleInstanceHandlerFactory(new ActionEventHandler<DynamicEventData>(async (d) =>
{
handleCount++;
await Task.CompletedTask;
})));
LocalEventBus.Subscribe(eventName,
new SingleInstanceHandlerFactory(new ActionEventHandler<DynamicEventData>(async (d) =>
{
handleCount++;
await Task.CompletedTask;
})));
await LocalEventBus.PublishAsync(eventName, new { Value = 1 });
handleCount.ShouldBe(2);
LocalEventBus.UnsubscribeAll(eventName);
// After UnsubscribeAll, publishing still works (key exists) but no handlers are invoked
await LocalEventBus.PublishAsync(eventName, new { Value = 2 });
handleCount.ShouldBe(2);
}
[Fact]
public async Task Should_Handle_Multiple_Dynamic_Events_Independently()
{
var countA = 0;
var countB = 0;
var eventNameA = "EventA-" + Guid.NewGuid().ToString("N");
var eventNameB = "EventB-" + Guid.NewGuid().ToString("N");
using var subA = LocalEventBus.Subscribe(eventNameA,
new SingleInstanceHandlerFactory(new ActionEventHandler<DynamicEventData>(async (d) =>
{
countA++;
await Task.CompletedTask;
})));
using var subB = LocalEventBus.Subscribe(eventNameB,
new SingleInstanceHandlerFactory(new ActionEventHandler<DynamicEventData>(async (d) =>
{
countB++;
await Task.CompletedTask;
})));
await LocalEventBus.PublishAsync(eventNameA, new { Value = 1 });
await LocalEventBus.PublishAsync(eventNameB, new { Value = 2 });
await LocalEventBus.PublishAsync(eventNameA, new { Value = 3 });
countA.ShouldBe(2);
countB.ShouldBe(1);
}
[Fact]
public async Task Should_Receive_EventName_In_DynamicEventData()
{
string? receivedEventName = null;
var eventName = "TestEvent-" + Guid.NewGuid().ToString("N");
using var subscription = LocalEventBus.Subscribe(eventName,
new SingleInstanceHandlerFactory(new ActionEventHandler<DynamicEventData>(async (d) =>
{
receivedEventName = d.EventName;
await Task.CompletedTask;
})));
await LocalEventBus.PublishAsync(eventName, new { Value = 1 });
receivedEventName.ShouldBe(eventName);
}
[Fact]
public async Task Should_Convert_Anonymous_Object_To_Typed_Handler()
{
MySimpleEventData? receivedData = null;
var eventName = EventNameAttribute.GetNameOrDefault<MySimpleEventData>();
using var subscription = LocalEventBus.Subscribe<MySimpleEventData>(async (d) =>
{
receivedData = d;
await Task.CompletedTask;
});
await LocalEventBus.PublishAsync(eventName, new { Value = 77 });
receivedData.ShouldNotBeNull();
receivedData.Value.ShouldBe(77);
}
}

28
test/DistEvents/DistDemoApp.EfCoreRabbitMq/DistDemoApp.EfCoreRabbitMq.csproj

@ -1,28 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<RootNamespace>DistDemoApp</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\framework\src\Volo.Abp.EntityFrameworkCore.SqlServer\Volo.Abp.EntityFrameworkCore.SqlServer.csproj" />
<ProjectReference Include="..\..\..\framework\src\Volo.Abp.EventBus.RabbitMQ\Volo.Abp.EventBus.RabbitMQ.csproj" />
<ProjectReference Include="..\DistDemoApp.Shared\DistDemoApp.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools">
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
<PrivateAssets>compile; contentFiles; build; buildMultitargeting; buildTransitive; analyzers; native</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

44
test/DistEvents/DistDemoApp.EfCoreRabbitMq/DistDemoAppEfCoreRabbitMqModule.cs

@ -1,44 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.DistributedEvents;
using Volo.Abp.EntityFrameworkCore.SqlServer;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.EventBus.RabbitMq;
using Volo.Abp.Modularity;
namespace DistDemoApp
{
[DependsOn(
typeof(AbpEntityFrameworkCoreSqlServerModule),
typeof(AbpEventBusRabbitMqModule),
typeof(DistDemoAppSharedModule)
)]
public class DistDemoAppEfCoreRabbitMqModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAbpDbContext<TodoDbContext>(options =>
{
options.AddDefaultRepositories();
});
Configure<AbpDbContextOptions>(options =>
{
options.UseSqlServer();
});
Configure<AbpDistributedEventBusOptions>(options =>
{
options.Outboxes.Configure(config =>
{
config.UseDbContext<TodoDbContext>();
});
options.Inboxes.Configure(config =>
{
config.UseDbContext<TodoDbContext>();
});
});
}
}
}

162
test/DistEvents/DistDemoApp.EfCoreRabbitMq/Migrations/20210910152547_Added_Boxes_Initial.Designer.cs

@ -1,162 +0,0 @@
// <auto-generated />
using System;
using DistDemoApp;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Volo.Abp.EntityFrameworkCore;
namespace DistDemoApp.Migrations
{
[DbContext(typeof(TodoDbContext))]
[Migration("20210910152547_Added_Boxes_Initial")]
partial class Added_Boxes_Initial
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer)
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("ProductVersion", "5.0.9")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("DistDemoApp.TodoItem", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasMaxLength(40)
.HasColumnType("nvarchar(40)")
.HasColumnName("ConcurrencyStamp");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<string>("ExtraProperties")
.HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties");
b.Property<string>("Text")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.HasKey("Id");
b.ToTable("TodoItems");
});
modelBuilder.Entity("DistDemoApp.TodoSummary", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasMaxLength(40)
.HasColumnType("nvarchar(40)")
.HasColumnName("ConcurrencyStamp");
b.Property<byte>("Day")
.HasColumnType("tinyint");
b.Property<string>("ExtraProperties")
.HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties");
b.Property<byte>("Month")
.HasColumnType("tinyint");
b.Property<int>("TotalCount")
.HasColumnType("int");
b.Property<int>("Year")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("TodoSummaries");
});
modelBuilder.Entity("Volo.Abp.EntityFrameworkCore.DistributedEvents.IncomingEventRecord", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<byte[]>("EventData")
.IsRequired()
.HasColumnType("varbinary(max)");
b.Property<string>("EventName")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("ExtraProperties")
.HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties");
b.Property<string>("MessageId")
.HasColumnType("nvarchar(450)");
b.Property<bool>("Processed")
.HasColumnType("bit");
b.Property<DateTime?>("ProcessedTime")
.HasColumnType("datetime2");
b.HasKey("Id");
b.HasIndex("MessageId");
b.HasIndex("Processed", "CreationTime");
b.ToTable("AbpEventInbox");
});
modelBuilder.Entity("Volo.Abp.EntityFrameworkCore.DistributedEvents.OutgoingEventRecord", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<byte[]>("EventData")
.IsRequired()
.HasColumnType("varbinary(max)");
b.Property<string>("EventName")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("ExtraProperties")
.HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties");
b.HasKey("Id");
b.ToTable("AbpEventOutbox");
});
#pragma warning restore 612, 618
}
}
}

103
test/DistEvents/DistDemoApp.EfCoreRabbitMq/Migrations/20210910152547_Added_Boxes_Initial.cs

@ -1,103 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace DistDemoApp.Migrations
{
public partial class Added_Boxes_Initial : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AbpEventInbox",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: true),
MessageId = table.Column<string>(type: "nvarchar(450)", nullable: true),
EventName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
EventData = table.Column<byte[]>(type: "varbinary(max)", nullable: false),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
Processed = table.Column<bool>(type: "bit", nullable: false),
ProcessedTime = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AbpEventInbox", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AbpEventOutbox",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: true),
EventName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
EventData = table.Column<byte[]>(type: "varbinary(max)", nullable: false),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AbpEventOutbox", x => x.Id);
});
migrationBuilder.CreateTable(
name: "TodoItems",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Text = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: true),
ConcurrencyStamp = table.Column<string>(type: "nvarchar(40)", maxLength: 40, nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_TodoItems", x => x.Id);
});
migrationBuilder.CreateTable(
name: "TodoSummaries",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Year = table.Column<int>(type: "int", nullable: false),
Month = table.Column<byte>(type: "tinyint", nullable: false),
Day = table.Column<byte>(type: "tinyint", nullable: false),
TotalCount = table.Column<int>(type: "int", nullable: false),
ExtraProperties = table.Column<string>(type: "nvarchar(max)", nullable: true),
ConcurrencyStamp = table.Column<string>(type: "nvarchar(40)", maxLength: 40, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_TodoSummaries", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_AbpEventInbox_MessageId",
table: "AbpEventInbox",
column: "MessageId");
migrationBuilder.CreateIndex(
name: "IX_AbpEventInbox_Processed_CreationTime",
table: "AbpEventInbox",
columns: new[] { "Processed", "CreationTime" });
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AbpEventInbox");
migrationBuilder.DropTable(
name: "AbpEventOutbox");
migrationBuilder.DropTable(
name: "TodoItems");
migrationBuilder.DropTable(
name: "TodoSummaries");
}
}
}

160
test/DistEvents/DistDemoApp.EfCoreRabbitMq/Migrations/TodoDbContextModelSnapshot.cs

@ -1,160 +0,0 @@
// <auto-generated />
using System;
using DistDemoApp;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Volo.Abp.EntityFrameworkCore;
namespace DistDemoApp.Migrations
{
[DbContext(typeof(TodoDbContext))]
partial class TodoDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer)
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("ProductVersion", "5.0.9")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("DistDemoApp.TodoItem", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasMaxLength(40)
.HasColumnType("nvarchar(40)")
.HasColumnName("ConcurrencyStamp");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<string>("ExtraProperties")
.HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties");
b.Property<string>("Text")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("nvarchar(128)");
b.HasKey("Id");
b.ToTable("TodoItems");
});
modelBuilder.Entity("DistDemoApp.TodoSummary", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasMaxLength(40)
.HasColumnType("nvarchar(40)")
.HasColumnName("ConcurrencyStamp");
b.Property<byte>("Day")
.HasColumnType("tinyint");
b.Property<string>("ExtraProperties")
.HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties");
b.Property<byte>("Month")
.HasColumnType("tinyint");
b.Property<int>("TotalCount")
.HasColumnType("int");
b.Property<int>("Year")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("TodoSummaries");
});
modelBuilder.Entity("Volo.Abp.EntityFrameworkCore.DistributedEvents.IncomingEventRecord", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<byte[]>("EventData")
.IsRequired()
.HasColumnType("varbinary(max)");
b.Property<string>("EventName")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("ExtraProperties")
.HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties");
b.Property<string>("MessageId")
.HasColumnType("nvarchar(450)");
b.Property<bool>("Processed")
.HasColumnType("bit");
b.Property<DateTime?>("ProcessedTime")
.HasColumnType("datetime2");
b.HasKey("Id");
b.HasIndex("MessageId");
b.HasIndex("Processed", "CreationTime");
b.ToTable("AbpEventInbox");
});
modelBuilder.Entity("Volo.Abp.EntityFrameworkCore.DistributedEvents.OutgoingEventRecord", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<byte[]>("EventData")
.IsRequired()
.HasColumnType("varbinary(max)");
b.Property<string>("EventName")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("ExtraProperties")
.HasColumnType("nvarchar(max)")
.HasColumnName("ExtraProperties");
b.HasKey("Id");
b.ToTable("AbpEventOutbox");
});
#pragma warning restore 612, 618
}
}
}

57
test/DistEvents/DistDemoApp.EfCoreRabbitMq/Program.cs

@ -1,57 +0,0 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;
using Serilog.Events;
namespace DistDemoApp
{
public class Program
{
public static async Task<int> Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
#if DEBUG
.MinimumLevel.Debug()
#else
.MinimumLevel.Information()
#endif
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.Enrich.FromLogContext()
.WriteTo.Async(c => c.File("Logs/logs.txt"))
.WriteTo.Async(c => c.Console())
.CreateLogger();
try
{
Log.Information("Starting console host.");
await CreateHostBuilder(args).RunConsoleAsync();
return 0;
}
catch (Exception ex)
{
Log.Fatal(ex, "Host terminated unexpectedly!");
return 1;
}
finally
{
Log.CloseAndFlush();
}
}
internal static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseAutofac()
.UseSerilog()
.ConfigureAppConfiguration((context, config) =>
{
//setup your additional configuration sources
})
.ConfigureServices((hostContext, services) =>
{
services.AddApplication<DistDemoAppEfCoreRabbitMqModule>();
});
}
}

34
test/DistEvents/DistDemoApp.EfCoreRabbitMq/TodoDbContext.cs

@ -1,34 +0,0 @@
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Domain.Entities;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.DistributedEvents;
namespace DistDemoApp
{
public class TodoDbContext : AbpDbContext<TodoDbContext>, IHasEventOutbox, IHasEventInbox
{
public DbSet<TodoItem> TodoItems { get; set; }
public DbSet<TodoSummary> TodoSummaries { get; set; }
public DbSet<OutgoingEventRecord> OutgoingEvents { get; set; }
public DbSet<IncomingEventRecord> IncomingEvents { get; set; }
public TodoDbContext(DbContextOptions<TodoDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ConfigureEventOutbox();
modelBuilder.ConfigureEventInbox();
modelBuilder.Entity<TodoItem>(b =>
{
b.Property(x => x.Text).IsRequired().HasMaxLength(128);
});
}
}
}

29
test/DistEvents/DistDemoApp.EfCoreRabbitMq/TodoDbContextFactory.cs

@ -1,29 +0,0 @@
using System.IO;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;
namespace DistDemoApp
{
public class TodoDbContextFactory : IDesignTimeDbContextFactory<TodoDbContext>
{
public TodoDbContext CreateDbContext(string[] args)
{
var configuration = BuildConfiguration();
var builder = new DbContextOptionsBuilder<TodoDbContext>()
.UseSqlServer(configuration.GetConnectionString("Default"));
return new TodoDbContext(builder.Options);
}
private static IConfigurationRoot BuildConfiguration()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false);
return builder.Build();
}
}
}

19
test/DistEvents/DistDemoApp.EfCoreRabbitMq/appsettings.json

@ -1,19 +0,0 @@
{
"ConnectionStrings": {
"Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=DistEventsDemo;Trusted_Connection=True;TrustServerCertificate=True"
},
"RabbitMQ": {
"Connections": {
"Default": {
"HostName": "localhost"
}
},
"EventBus": {
"ClientName": "DistDemoApp",
"ExchangeName": "DistDemo"
}
},
"Redis": {
"Configuration": "127.0.0.1"
}
}

21
test/DistEvents/DistDemoApp.MongoDbKafka/DistDemoApp.MongoDbKafka.csproj

@ -1,21 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<RootNamespace>DistDemoApp</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\framework\src\Volo.Abp.MongoDB\Volo.Abp.MongoDB.csproj" />
<ProjectReference Include="..\..\..\framework\src\Volo.Abp.EventBus.Kafka\Volo.Abp.EventBus.Kafka.csproj" />
<ProjectReference Include="..\DistDemoApp.Shared\DistDemoApp.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

38
test/DistEvents/DistDemoApp.MongoDbKafka/DistDemoAppMongoDbKafkaModule.cs

@ -1,38 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.EventBus.Kafka;
using Volo.Abp.Modularity;
using Volo.Abp.MongoDB;
using Volo.Abp.MongoDB.DistributedEvents;
namespace DistDemoApp
{
[DependsOn(
typeof(AbpMongoDbModule),
typeof(AbpEventBusKafkaModule),
typeof(DistDemoAppSharedModule)
)]
public class DistDemoAppMongoDbKafkaModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddMongoDbContext<TodoMongoDbContext>(options =>
{
options.AddDefaultRepositories();
});
Configure<AbpDistributedEventBusOptions>(options =>
{
options.Outboxes.Configure(config =>
{
config.UseMongoDbContext<TodoMongoDbContext>();
});
options.Inboxes.Configure(config =>
{
config.UseMongoDbContext<TodoMongoDbContext>();
});
});
}
}
}

57
test/DistEvents/DistDemoApp.MongoDbKafka/Program.cs

@ -1,57 +0,0 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;
using Serilog.Events;
namespace DistDemoApp
{
public class Program
{
public static async Task<int> Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
#if DEBUG
.MinimumLevel.Debug()
#else
.MinimumLevel.Information()
#endif
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.Enrich.FromLogContext()
.WriteTo.Async(c => c.File("Logs/logs.txt"))
.WriteTo.Async(c => c.Console())
.CreateLogger();
try
{
Log.Information("Starting console host.");
await CreateHostBuilder(args).RunConsoleAsync();
return 0;
}
catch (Exception ex)
{
Log.Fatal(ex, "Host terminated unexpectedly!");
return 1;
}
finally
{
Log.CloseAndFlush();
}
}
internal static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseAutofac()
.UseSerilog()
.ConfigureAppConfiguration((context, config) =>
{
//setup your additional configuration sources
})
.ConfigureServices((hostContext, services) =>
{
services.AddApplication<DistDemoAppMongoDbKafkaModule>();
});
}
}

26
test/DistEvents/DistDemoApp.MongoDbKafka/TodoMongoDbContext.cs

@ -1,26 +0,0 @@
using MongoDB.Driver;
using Volo.Abp.Data;
using Volo.Abp.MongoDB;
using Volo.Abp.MongoDB.DistributedEvents;
namespace DistDemoApp
{
[ConnectionStringName("Default")]
public class TodoMongoDbContext : AbpMongoDbContext, IHasEventOutbox, IHasEventInbox
{
public IMongoCollection<TodoItem> TodoItems => Collection<TodoItem>();
public IMongoCollection<TodoSummary> TodoSummaries => Collection<TodoSummary>();
public IMongoCollection<OutgoingEventRecord> OutgoingEvents
{
get => Collection<OutgoingEventRecord>();
set {}
}
public IMongoCollection<IncomingEventRecord> IncomingEvents
{
get => Collection<IncomingEventRecord>();
set {}
}
}
}

19
test/DistEvents/DistDemoApp.MongoDbKafka/appsettings.json

@ -1,19 +0,0 @@
{
"ConnectionStrings": {
"Default": "mongodb://localhost:27018,localhost:27019,localhost:27020/DistEventsDemo"
},
"Kafka": {
"Connections": {
"Default": {
"BootstrapServers": "localhost:9092"
}
},
"EventBus": {
"GroupId": "DistDemoApp",
"TopicName": "DistDemoTopic"
}
},
"Redis": {
"Configuration": "127.0.0.1"
}
}

21
test/DistEvents/DistDemoApp.MongoDbRebus/DistDemoApp.MongoDbRebus.csproj

@ -1,21 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<RootNamespace>DistDemoApp</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\framework\src\Volo.Abp.MongoDB\Volo.Abp.MongoDB.csproj" />
<ProjectReference Include="..\..\..\framework\src\Volo.Abp.EventBus.Rebus\Volo.Abp.EventBus.Rebus.csproj" />
<ProjectReference Include="..\DistDemoApp.Shared\DistDemoApp.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

53
test/DistEvents/DistDemoApp.MongoDbRebus/DistDemoAppMongoDbRebusModule.cs

@ -1,53 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using Rebus.Persistence.InMem;
using Rebus.Transport.InMem;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.EventBus.Rebus;
using Volo.Abp.Modularity;
using Volo.Abp.MongoDB;
using Volo.Abp.MongoDB.DistributedEvents;
namespace DistDemoApp
{
[DependsOn(
typeof(AbpMongoDbModule),
typeof(AbpEventBusRebusModule),
typeof(DistDemoAppSharedModule)
)]
public class DistDemoAppMongoDbRebusModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<AbpRebusEventBusOptions>(options =>
{
options.InputQueueName = "eventbus";
options.Configurer = rebusConfigurer =>
{
rebusConfigurer.Transport(t => t.UseInMemoryTransport(new InMemNetwork(), "eventbus"));
rebusConfigurer.Subscriptions(s => s.StoreInMemory());
};
});
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddMongoDbContext<TodoMongoDbContext>(options =>
{
options.AddDefaultRepositories();
});
Configure<AbpDistributedEventBusOptions>(options =>
{
options.Outboxes.Configure(config =>
{
config.UseMongoDbContext<TodoMongoDbContext>();
});
options.Inboxes.Configure(config =>
{
config.UseMongoDbContext<TodoMongoDbContext>();
});
});
}
}
}

57
test/DistEvents/DistDemoApp.MongoDbRebus/Program.cs

@ -1,57 +0,0 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;
using Serilog.Events;
namespace DistDemoApp
{
public class Program
{
public static async Task<int> Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
#if DEBUG
.MinimumLevel.Debug()
#else
.MinimumLevel.Information()
#endif
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.Enrich.FromLogContext()
.WriteTo.Async(c => c.File("Logs/logs.txt"))
.WriteTo.Async(c => c.Console())
.CreateLogger();
try
{
Log.Information("Starting console host.");
await CreateHostBuilder(args).RunConsoleAsync();
return 0;
}
catch (Exception ex)
{
Log.Fatal(ex, "Host terminated unexpectedly!");
return 1;
}
finally
{
Log.CloseAndFlush();
}
}
internal static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseAutofac()
.UseSerilog()
.ConfigureAppConfiguration((context, config) =>
{
//setup your additional configuration sources
})
.ConfigureServices((hostContext, services) =>
{
services.AddApplication<DistDemoAppMongoDbRebusModule>();
});
}
}

19
test/DistEvents/DistDemoApp.MongoDbRebus/TodoMongoDbContext.cs

@ -1,19 +0,0 @@
using MongoDB.Driver;
using Volo.Abp.Data;
using Volo.Abp.MongoDB;
using Volo.Abp.MongoDB.DistributedEvents;
namespace DistDemoApp
{
[ConnectionStringName("Default")]
public class TodoMongoDbContext : AbpMongoDbContext, IHasEventOutbox, IHasEventInbox
{
public IMongoCollection<TodoItem> TodoItems => Collection<TodoItem>();
public IMongoCollection<TodoSummary> TodoSummaries => Collection<TodoSummary>();
public IMongoCollection<OutgoingEventRecord> OutgoingEvents => Collection<OutgoingEventRecord>();
public IMongoCollection<IncomingEventRecord> IncomingEvents => Collection<IncomingEventRecord>();
}
}

19
test/DistEvents/DistDemoApp.MongoDbRebus/appsettings.json

@ -1,19 +0,0 @@
{
"ConnectionStrings": {
"Default": "mongodb://localhost:27018,localhost:27019,localhost:27020/DistEventsDemo"
},
"Kafka": {
"Connections": {
"Default": {
"BootstrapServers": "localhost:9092"
}
},
"EventBus": {
"GroupId": "DistDemoApp",
"TopicName": "DistDemoTopic"
}
},
"Redis": {
"Configuration": "127.0.0.1"
}
}

29
test/DistEvents/DistDemoApp.Shared/DemoService.cs

@ -1,29 +0,0 @@
using System;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
namespace DistDemoApp
{
public class DemoService : ITransientDependency
{
private readonly IRepository<TodoItem, Guid> _todoItemRepository;
public DemoService(IRepository<TodoItem, Guid> todoItemRepository)
{
_todoItemRepository = todoItemRepository;
}
public async Task CreateTodoItemAsync()
{
var todoItem = await _todoItemRepository.InsertAsync(
new TodoItem
{
Text = "todo item " + DateTime.Now.Ticks
}
);
Console.WriteLine("Created a new todo item: " + todoItem);
}
}
}

23
test/DistEvents/DistDemoApp.Shared/DistDemoApp.Shared.csproj

@ -1,23 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<RootNamespace>DistDemoApp</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DistributedLock.Redis" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
<PackageReference Include="Serilog.Extensions.Hosting" />
<PackageReference Include="Serilog.Sinks.Async" />
<PackageReference Include="Serilog.Sinks.Console" />
<PackageReference Include="Serilog.Sinks.File" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\framework\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<ProjectReference Include="..\..\..\framework\src\Volo.Abp.Ddd.Domain\Volo.Abp.Ddd.Domain.csproj" />
<ProjectReference Include="..\..\..\framework\src\Volo.Abp.EventBus\Volo.Abp.EventBus.csproj" />
</ItemGroup>
</Project>

39
test/DistEvents/DistDemoApp.Shared/DistDemoAppHostedService.cs

@ -1,39 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Volo.Abp;
namespace DistDemoApp
{
public class DistDemoAppHostedService : IHostedService
{
private readonly IAbpApplicationWithExternalServiceProvider _application;
private readonly IServiceProvider _serviceProvider;
private readonly DemoService _demoService;
public DistDemoAppHostedService(
IAbpApplicationWithExternalServiceProvider application,
IServiceProvider serviceProvider,
DemoService demoService)
{
_application = application;
_serviceProvider = serviceProvider;
_demoService = demoService;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
_application.Initialize(_serviceProvider);
await _demoService.CreateTodoItemAsync();
}
public Task StopAsync(CancellationToken cancellationToken)
{
_application.Shutdown();
return Task.CompletedTask;
}
}
}

39
test/DistEvents/DistDemoApp.Shared/DistDemoAppSharedModule.cs

@ -1,39 +0,0 @@
using Medallion.Threading;
using Medallion.Threading.Redis;
using Microsoft.Extensions.DependencyInjection;
using StackExchange.Redis;
using Volo.Abp.Autofac;
using Volo.Abp.Domain;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.EventBus;
using Volo.Abp.Modularity;
namespace DistDemoApp
{
[DependsOn(
typeof(AbpAutofacModule),
typeof(AbpDddDomainModule),
typeof(AbpEventBusModule)
)]
public class DistDemoAppSharedModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
context.Services.AddHostedService<DistDemoAppHostedService>();
Configure<AbpDistributedEntityEventOptions>(options =>
{
options.EtoMappings.Add<TodoItem, TodoItemEto>();
options.AutoEventSelectors.Add<TodoItem>();
});
context.Services.AddSingleton<IDistributedLockProvider>(sp =>
{
var connection = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]);
return new RedisDistributedSynchronizationProvider(connection.GetDatabase());
});
}
}
}

63
test/DistEvents/DistDemoApp.Shared/TodoEventHandler.cs

@ -1,63 +0,0 @@
using System;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events.Distributed;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Uow;
namespace DistDemoApp
{
public class TodoEventHandler :
IDistributedEventHandler<EntityCreatedEto<TodoItemEto>>,
IDistributedEventHandler<EntityDeletedEto<TodoItemEto>>,
ITransientDependency
{
private readonly IRepository<TodoSummary, int> _todoSummaryRepository;
public TodoEventHandler(IRepository<TodoSummary, int> todoSummaryRepository)
{
_todoSummaryRepository = todoSummaryRepository;
}
[UnitOfWork]
public virtual async Task HandleEventAsync(EntityCreatedEto<TodoItemEto> eventData)
{
var dateTime = eventData.Entity.CreationTime;
var todoSummary = await _todoSummaryRepository.FindAsync(
x => x.Year == dateTime.Year &&
x.Month == dateTime.Month &&
x.Day == dateTime.Day
);
if (todoSummary == null)
{
todoSummary = await _todoSummaryRepository.InsertAsync(new TodoSummary(dateTime));
}
else
{
todoSummary.Increase();
await _todoSummaryRepository.UpdateAsync(todoSummary);
}
Console.WriteLine("Increased total count: " + todoSummary);
}
public async Task HandleEventAsync(EntityDeletedEto<TodoItemEto> eventData)
{
var dateTime = eventData.Entity.CreationTime;
var todoSummary = await _todoSummaryRepository.FirstOrDefaultAsync(
x => x.Year == dateTime.Year &&
x.Month == dateTime.Month &&
x.Day == dateTime.Day
);
if (todoSummary != null)
{
todoSummary.Decrease();
await _todoSummaryRepository.UpdateAsync(todoSummary);
Console.WriteLine("Decreased total count: " + todoSummary);
}
}
}
}

15
test/DistEvents/DistDemoApp.Shared/TodoItem.cs

@ -1,15 +0,0 @@
using System;
using Volo.Abp.Domain.Entities.Auditing;
namespace DistDemoApp
{
public class TodoItem : CreationAuditedAggregateRoot<Guid>
{
public string Text { get; set; }
public override string ToString()
{
return $"{base.ToString()}, Text = {Text}";
}
}
}

12
test/DistEvents/DistDemoApp.Shared/TodoItemEto.cs

@ -1,12 +0,0 @@
using System;
using Volo.Abp.EventBus;
namespace DistDemoApp
{
[EventName("todo-item")]
public class TodoItemEto
{
public DateTime CreationTime { get; set; }
public string Text { get; set; }
}
}

24
test/DistEvents/DistDemoApp.Shared/TodoItemObjectMapper.cs

@ -1,24 +0,0 @@
using Volo.Abp.DependencyInjection;
using Volo.Abp.ObjectMapping;
namespace DistDemoApp
{
public class TodoItemObjectMapper : IObjectMapper<TodoItem, TodoItemEto>, ISingletonDependency
{
public TodoItemEto Map(TodoItem source)
{
return new TodoItemEto
{
Text = source.Text,
CreationTime = source.CreationTime
};
}
public TodoItemEto Map(TodoItem source, TodoItemEto destination)
{
destination.Text = source.Text;
destination.CreationTime = source.CreationTime;
return destination;
}
}
}

41
test/DistEvents/DistDemoApp.Shared/TodoSummary.cs

@ -1,41 +0,0 @@
using System;
using Volo.Abp.Domain.Entities;
namespace DistDemoApp
{
public class TodoSummary : AggregateRoot<int>
{
public int Year { get; private set; }
public byte Month { get; private set; }
public byte Day { get; private set; }
public int TotalCount { get; private set; }
private TodoSummary()
{
}
public TodoSummary(DateTime dateTime, int initialCount = 1)
{
Year = dateTime.Year;
Month = (byte)dateTime.Month;
Day = (byte)dateTime.Day;
TotalCount = initialCount;
}
public void Increase(int amount = 1)
{
TotalCount += amount;
}
public void Decrease(int amount = 1)
{
TotalCount -= amount;
}
public override string ToString()
{
return $"{base.ToString()}, {Year}-{Month:00}-{Day:00}: {TotalCount}";
}
}
}

6
test/DistEvents/DistEventsDemo.slnx

@ -1,6 +0,0 @@
<Solution>
<Project Path="DistDemoApp.EfCoreRabbitMq/DistDemoApp.EfCoreRabbitMq.csproj" />
<Project Path="DistDemoApp.MongoDbKafka/DistDemoApp.MongoDbKafka.csproj" />
<Project Path="DistDemoApp.MongoDbRebus/DistDemoApp.MongoDbRebus.csproj" />
<Project Path="DistDemoApp.Shared/DistDemoApp.Shared.csproj" />
</Solution>
Loading…
Cancel
Save