Browse Source

refactor: Update DynamicEventData handling and improve documentation across event bus implementations

pull/25023/head
maliming 1 week ago
parent
commit
29dee66e90
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 18
      docs/en/framework/infrastructure/event-bus/distributed/index.md
  2. 16
      docs/en/framework/infrastructure/event-bus/local/index.md
  3. 101
      framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/DynamicEventData.cs
  4. 9
      framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs
  5. 2
      framework/src/Volo.Abp.EventBus.Dapr/Volo/Abp/EventBus/Dapr/DaprDistributedEventBus.cs
  6. 8
      framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs
  7. 5
      framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs
  8. 2
      framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs
  9. 2
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs
  10. 12
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus.cs
  11. 16
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs
  12. 2
      framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventBus.cs
  13. 80
      framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus_Test.cs
  14. 110
      framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Local/LocalEventBus_Dynamic_Test.cs

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

@ -763,14 +763,9 @@ var subscription = distributedEventBus.Subscribe(
new SingleInstanceHandlerFactory(
new ActionEventHandler<DynamicEventData>(eventData =>
{
// Access the event name
// Access the event name and raw data
var name = eventData.EventName;
// Convert to a loosely-typed object (Dictionary/List/primitives)
var obj = eventData.ConvertToTypedObject();
// Or convert to a strongly-typed object
var typed = eventData.ConvertToTypedObject<MyEventDto>();
var data = eventData.Data;
return Task.CompletedTask;
})));
@ -789,17 +784,16 @@ Where `myDistributedEventHandler` implements `IDistributedEventHandler<DynamicEv
### 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 deserialized typed data, while the dynamic handler receives a `DynamicEventData` wrapper.
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 wraps the event payload with a string-based event name:
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.
- **`ConvertToTypedObject()`**: Converts data to a loosely-typed object graph (dictionaries, lists, primitives).
- **`ConvertToTypedObject<T>()`**: Deserializes data to a strongly-typed `T` object.
- **`ConvertToTypedObject(Type type)`**: Deserializes data to the specified type.
> 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

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

@ -280,14 +280,9 @@ var subscription = localEventBus.Subscribe(
new SingleInstanceHandlerFactory(
new ActionEventHandler<DynamicEventData>(eventData =>
{
// Access the event name
// Access the event name and raw data
var name = eventData.EventName;
// Convert to a loosely-typed object (Dictionary/List/primitives)
var obj = eventData.ConvertToTypedObject();
// Or convert to a strongly-typed object
var typed = eventData.ConvertToTypedObject<MyEventDto>();
var data = eventData.Data;
return Task.CompletedTask;
})));
@ -296,6 +291,13 @@ var subscription = localEventBus.Subscribe(
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.

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

@ -1,8 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
namespace Volo.Abp.EventBus;
/// <summary>
@ -14,105 +9,9 @@ public class DynamicEventData
public object Data { get; }
private JsonElement? _cachedJsonElement;
public DynamicEventData(string eventName, object data)
{
EventName = Check.NotNullOrWhiteSpace(eventName, nameof(eventName));
Data = Check.NotNull(data, nameof(data));
}
/// <summary>
/// Converts <see cref="Data"/> to a loosely-typed object graph
/// (dictionaries for objects, lists for arrays, primitives for values).
/// </summary>
public object ConvertToTypedObject()
{
return ConvertElement(GetJsonElement());
}
/// <summary>
/// Converts <see cref="Data"/> to a strongly-typed <typeparamref name="T"/> object.
/// Returns the data directly if it is already of type <typeparamref name="T"/>.
/// </summary>
public T ConvertToTypedObject<T>()
{
if (Data is T typedData)
{
return typedData;
}
return GetJsonElement().Deserialize<T>()
?? throw new InvalidOperationException($"Failed to deserialize DynamicEventData to {typeof(T).FullName}.");
}
/// <summary>
/// Converts <see cref="Data"/> to the specified <paramref name="type"/>.
/// Returns the data directly if it is already an instance of the target type.
/// </summary>
public object ConvertToTypedObject(Type type)
{
if (type.IsInstanceOfType(Data))
{
return Data;
}
return GetJsonElement().Deserialize(type)
?? throw new InvalidOperationException($"Failed to deserialize DynamicEventData to {type.FullName}.");
}
private JsonElement GetJsonElement()
{
if (_cachedJsonElement.HasValue)
{
return _cachedJsonElement.Value;
}
if (Data is JsonElement existingElement)
{
_cachedJsonElement = existingElement;
return existingElement;
}
_cachedJsonElement = JsonSerializer.SerializeToElement(Data);
return _cachedJsonElement.Value;
}
private static object ConvertElement(JsonElement element)
{
switch (element.ValueKind)
{
case JsonValueKind.Object:
{
var obj = new Dictionary<string, object?>();
foreach (var property in element.EnumerateObject())
{
obj[property.Name] = property.Value.ValueKind == JsonValueKind.Null
? null
: ConvertElement(property.Value);
}
return obj;
}
case JsonValueKind.Array:
return element.EnumerateArray()
.Select(item => item.ValueKind == JsonValueKind.Null ? null : (object?)ConvertElement(item))
.ToList();
case JsonValueKind.String:
return element.GetString()!;
case JsonValueKind.Number when element.TryGetInt64(out var longValue):
return longValue;
case JsonValueKind.Number when element.TryGetDecimal(out var decimalValue):
return decimalValue;
case JsonValueKind.Number when element.TryGetDouble(out var doubleValue):
return doubleValue;
case JsonValueKind.True:
return true;
case JsonValueKind.False:
return false;
case JsonValueKind.Null:
case JsonValueKind.Undefined:
default:
return null!;
}
}
}

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

@ -94,8 +94,8 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen
}
else if (DynamicHandlerFactories.ContainsKey(eventName))
{
var data = Serializer.Deserialize<object>(message.Body.ToArray());
eventData = new DynamicEventData(eventName, data);
var rawBytes = message.Body.ToArray();
eventData = new DynamicEventData(eventName, Serializer.Deserialize<object>(rawBytes));
eventType = typeof(DynamicEventData);
}
else
@ -204,7 +204,7 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen
if (eventType != null)
{
return PublishAsync(eventType, dynamicEventData.ConvertToTypedObject(eventType), onUnitOfWorkComplete);
return PublishAsync(eventType, ConvertDynamicEventData(dynamicEventData.Data, eventType), onUnitOfWorkComplete);
}
if (DynamicHandlerFactories.ContainsKey(eventName))
@ -296,8 +296,7 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen
}
else if (DynamicHandlerFactories.ContainsKey(incomingEvent.EventName))
{
var element = Serializer.Deserialize<object>(incomingEvent.EventData);
eventData = new DynamicEventData(incomingEvent.EventName, element);
eventData = new DynamicEventData(incomingEvent.EventName, Serializer.Deserialize<object>(incomingEvent.EventData));
eventType = typeof(DynamicEventData);
}
else

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

@ -154,7 +154,7 @@ public class DaprDistributedEventBus : DistributedEventBusBase, ISingletonDepend
if (eventType != null)
{
return PublishAsync(eventType, dynamicEventData.ConvertToTypedObject(eventType), onUnitOfWorkComplete);
return PublishAsync(eventType, ConvertDynamicEventData(dynamicEventData.Data, eventType), onUnitOfWorkComplete);
}
if (DynamicHandlerFactories.ContainsKey(eventName))

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

@ -94,8 +94,7 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen
}
else if (DynamicHandlerFactories.ContainsKey(eventName))
{
var element = Serializer.Deserialize<object>(message.Value);
eventData = new DynamicEventData(eventName, element);
eventData = new DynamicEventData(eventName, Serializer.Deserialize<object>(message.Value));
eventType = typeof(DynamicEventData);
}
else
@ -205,7 +204,7 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen
if (eventType != null)
{
return PublishAsync(eventType, dynamicEventData.ConvertToTypedObject(eventType), onUnitOfWorkComplete);
return PublishAsync(eventType, ConvertDynamicEventData(dynamicEventData.Data, eventType), onUnitOfWorkComplete);
}
if (DynamicHandlerFactories.ContainsKey(eventName))
@ -334,8 +333,7 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen
}
else if (DynamicHandlerFactories.ContainsKey(incomingEvent.EventName))
{
var element = Serializer.Deserialize<object>(incomingEvent.EventData);
eventData = new DynamicEventData(incomingEvent.EventName, element);
eventData = new DynamicEventData(incomingEvent.EventName, Serializer.Deserialize<object>(incomingEvent.EventData));
eventType = typeof(DynamicEventData);
}
else

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

@ -111,8 +111,9 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, IRabbitMqDis
}
else if (DynamicHandlerFactories.ContainsKey(eventName))
{
var rawBytes = ea.Body.ToArray();
eventType = typeof(DynamicEventData);
eventData = new DynamicEventData(eventName, Serializer.Deserialize<object>(ea.Body.ToArray()));
eventData = new DynamicEventData(eventName, Serializer.Deserialize<object>(rawBytes));
}
else
{
@ -232,7 +233,7 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, IRabbitMqDis
if (eventType != null)
{
return PublishAsync(eventType, dynamicEventData.ConvertToTypedObject(eventType), onUnitOfWorkComplete);
return PublishAsync(eventType, ConvertDynamicEventData(dynamicEventData.Data, eventType), onUnitOfWorkComplete);
}
if (DynamicHandlerFactories.ContainsKey(eventName))

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

@ -198,7 +198,7 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen
if (eventType != null)
{
return PublishAsync(eventType, dynamicEventData.ConvertToTypedObject(eventType), onUnitOfWorkComplete);
return PublishAsync(eventType, ConvertDynamicEventData(dynamicEventData.Data, eventType), onUnitOfWorkComplete);
}
if (DynamicHandlerFactories.ContainsKey(eventName))

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

@ -117,7 +117,7 @@ public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventB
if (eventType != null)
{
return PublishAsync(eventType, dynamicEventData.ConvertToTypedObject(eventType), onUnitOfWorkComplete, useOutbox);
return PublishAsync(eventType, ConvertDynamicEventData(dynamicEventData.Data, eventType), onUnitOfWorkComplete, useOutbox);
}
return PublishAsync(typeof(DynamicEventData), dynamicEventData, onUnitOfWorkComplete, useOutbox);

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

@ -177,7 +177,7 @@ public class LocalDistributedEventBus : DistributedEventBusBase, ISingletonDepen
if (eventType != null)
{
return PublishAsync(eventType, dynamicEventData.ConvertToTypedObject(eventType), onUnitOfWorkComplete, useOutbox);
return PublishAsync(eventType, ConvertDynamicEventData(dynamicEventData.Data, eventType), onUnitOfWorkComplete, useOutbox);
}
if (!DynamicEventNames.ContainsKey(eventName))
@ -236,11 +236,11 @@ public class LocalDistributedEventBus : DistributedEventBusBase, ISingletonDepen
{
eventData = new DynamicEventData(
outgoingEvent.EventName,
JsonSerializer.Deserialize<object>(outgoingEvent.EventData)!);
System.Text.Json.JsonSerializer.Deserialize<object>(outgoingEvent.EventData)!);
}
else
{
eventData = JsonSerializer.Deserialize(outgoingEvent.EventData, eventType)!;
eventData = System.Text.Json.JsonSerializer.Deserialize(outgoingEvent.EventData, eventType)!;
}
if (await AddToInboxAsync(Guid.NewGuid().ToString(), outgoingEvent.EventName, eventType, eventData, null))
@ -278,11 +278,11 @@ public class LocalDistributedEventBus : DistributedEventBusBase, ISingletonDepen
{
eventData = new DynamicEventData(
incomingEvent.EventName,
JsonSerializer.Deserialize<object>(incomingEvent.EventData)!);
System.Text.Json.JsonSerializer.Deserialize<object>(incomingEvent.EventData)!);
}
else
{
eventData = JsonSerializer.Deserialize(incomingEvent.EventData, eventType)!;
eventData = System.Text.Json.JsonSerializer.Deserialize(incomingEvent.EventData, eventType)!;
}
var exceptions = new List<Exception>();
@ -298,7 +298,7 @@ public class LocalDistributedEventBus : DistributedEventBusBase, ISingletonDepen
protected override byte[] Serialize(object eventData)
{
return JsonSerializer.SerializeToUtf8Bytes(eventData);
return System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(eventData);
}
protected override Task OnAddToOutboxAsync(string eventName, Type eventType, object eventData)

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

@ -6,6 +6,7 @@ using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using System.Text.Json;
using Volo.Abp.Collections;
using Volo.Abp.DynamicProxy;
using Volo.Abp.EventBus.Distributed;
@ -175,7 +176,7 @@ public abstract class EventBusBase : IEventBus
typeof(IEventDataWithInheritableGenericArgument).IsAssignableFrom(actualEventType))
{
var resolvedEventData = eventData is DynamicEventData aed
? aed.ConvertToTypedObject(actualEventType)
? ConvertDynamicEventData(aed.Data, actualEventType)
: eventData;
var genericArg = actualEventType.GetGenericArguments()[0];
@ -209,7 +210,7 @@ public abstract class EventBusBase : IEventBus
{
if (eventData is DynamicEventData dynamicEventData && handlerEventType != typeof(DynamicEventData))
{
return dynamicEventData.ConvertToTypedObject(handlerEventType);
return ConvertDynamicEventData(dynamicEventData.Data, handlerEventType);
}
if (handlerEventType == typeof(DynamicEventData) && eventData is not DynamicEventData)
@ -220,6 +221,17 @@ public abstract class EventBusBase : IEventBus
return eventData;
}
protected virtual object ConvertDynamicEventData(object data, Type targetType)
{
if (targetType.IsInstanceOfType(data))
{
return data;
}
var json = JsonSerializer.Serialize(data);
return JsonSerializer.Deserialize(json, targetType)!;
}
protected void ThrowOriginalExceptions(Type eventType, List<Exception> exceptions)
{
if (exceptions.Count == 1)

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

@ -178,7 +178,7 @@ public class LocalEventBus : EventBusBase, ILocalEventBus, ISingletonDependency
if (eventType != null)
{
return PublishAsync(eventType, dynamicEventData.ConvertToTypedObject(eventType), onUnitOfWorkComplete);
return PublishAsync(eventType, ConvertDynamicEventData(dynamicEventData.Data, eventType), onUnitOfWorkComplete);
}
var isDynamic = DynamicEventHandlerFactories.ContainsKey(eventName);

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

@ -82,7 +82,7 @@ public class LocalDistributedEventBus_Test : LocalDistributedEventBusTestBase
new SingleInstanceHandlerFactory(new ActionEventHandler<DynamicEventData>(async (d) =>
{
handleCount++;
d.ConvertToTypedObject().ShouldNotBeNull();
d.Data.ShouldNotBeNull();
await Task.CompletedTask;
})));
@ -211,6 +211,69 @@ public class LocalDistributedEventBus_Test : LocalDistributedEventBusTestBase
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()
{
@ -309,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;
}
}
}

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

@ -125,7 +125,7 @@ public class LocalEventBus_Dynamic_Test : EventBusTestBase
}
[Fact]
public async Task Should_ConvertToTypedObject_In_Dynamic_Handler()
public async Task Should_Access_Data_In_Dynamic_Handler()
{
object? receivedData = null;
var eventName = "TestEvent-" + Guid.NewGuid().ToString("N");
@ -133,34 +133,126 @@ public class LocalEventBus_Dynamic_Test : EventBusTestBase
using var subscription = LocalEventBus.Subscribe(eventName,
new SingleInstanceHandlerFactory(new ActionEventHandler<DynamicEventData>(async (d) =>
{
receivedData = d.ConvertToTypedObject();
receivedData = d.Data;
await Task.CompletedTask;
})));
await LocalEventBus.PublishAsync(eventName, new { Name = "Hello", Count = 42 });
receivedData.ShouldNotBeNull();
var dict = receivedData.ShouldBeOfType<Dictionary<string, object?>>();
dict["Name"].ShouldBe("Hello");
dict["Count"].ShouldBe(42L);
}
[Fact]
public async Task Should_ConvertToTypedObject_Generic_In_Dynamic_Handler()
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) =>
{
receivedData = d.ConvertToTypedObject<MySimpleEventData>();
receivedEventName = d.EventName;
await Task.CompletedTask;
})));
await LocalEventBus.PublishAsync(eventName, new MySimpleEventData(99));
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(99);
receivedData.Value.ShouldBe(77);
}
}

Loading…
Cancel
Save