From 29dee66e908261fa6d52c12ba39011e69221dbd6 Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 22 Mar 2026 19:45:21 +0800 Subject: [PATCH] refactor: Update DynamicEventData handling and improve documentation across event bus implementations --- .../event-bus/distributed/index.md | 18 +-- .../infrastructure/event-bus/local/index.md | 16 +-- .../Volo/Abp/EventBus/DynamicEventData.cs | 101 ---------------- .../Azure/AzureDistributedEventBus.cs | 9 +- .../EventBus/Dapr/DaprDistributedEventBus.cs | 2 +- .../Kafka/KafkaDistributedEventBus.cs | 8 +- .../RabbitMq/RabbitMqDistributedEventBus.cs | 5 +- .../Rebus/RebusDistributedEventBus.cs | 2 +- .../Distributed/DistributedEventBusBase.cs | 2 +- .../Distributed/LocalDistributedEventBus.cs | 12 +- .../Volo/Abp/EventBus/EventBusBase.cs | 16 ++- .../Volo/Abp/EventBus/Local/LocalEventBus.cs | 2 +- .../LocalDistributedEventBus_Test.cs | 80 ++++++++++++- .../Local/LocalEventBus_Dynamic_Test.cs | 110 ++++++++++++++++-- 14 files changed, 229 insertions(+), 154 deletions(-) diff --git a/docs/en/framework/infrastructure/event-bus/distributed/index.md b/docs/en/framework/infrastructure/event-bus/distributed/index.md index 8fbf91465e..74179bb30c 100644 --- a/docs/en/framework/infrastructure/event-bus/distributed/index.md +++ b/docs/en/framework/infrastructure/event-bus/distributed/index.md @@ -763,14 +763,9 @@ var subscription = distributedEventBus.Subscribe( new SingleInstanceHandlerFactory( new ActionEventHandler(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(); + var data = eventData.Data; return Task.CompletedTask; }))); @@ -789,17 +784,16 @@ Where `myDistributedEventHandler` implements `IDistributedEventHandler()`**: 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 diff --git a/docs/en/framework/infrastructure/event-bus/local/index.md b/docs/en/framework/infrastructure/event-bus/local/index.md index 8545797049..bc8af41a06 100644 --- a/docs/en/framework/infrastructure/event-bus/local/index.md +++ b/docs/en/framework/infrastructure/event-bus/local/index.md @@ -280,14 +280,9 @@ var subscription = localEventBus.Subscribe( new SingleInstanceHandlerFactory( new ActionEventHandler(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(); + 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. diff --git a/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/DynamicEventData.cs b/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/DynamicEventData.cs index 536102ddfc..1af2f600a3 100644 --- a/framework/src/Volo.Abp.EventBus.Abstractions/Volo/Abp/EventBus/DynamicEventData.cs +++ b/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; /// @@ -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)); } - - /// - /// Converts to a loosely-typed object graph - /// (dictionaries for objects, lists for arrays, primitives for values). - /// - public object ConvertToTypedObject() - { - return ConvertElement(GetJsonElement()); - } - - /// - /// Converts to a strongly-typed object. - /// Returns the data directly if it is already of type . - /// - public T ConvertToTypedObject() - { - if (Data is T typedData) - { - return typedData; - } - - return GetJsonElement().Deserialize() - ?? throw new InvalidOperationException($"Failed to deserialize DynamicEventData to {typeof(T).FullName}."); - } - - /// - /// Converts to the specified . - /// Returns the data directly if it is already an instance of the target type. - /// - 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(); - 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!; - } - } } diff --git a/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs index 715cf6c003..e613502018 100644 --- a/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs +++ b/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(message.Body.ToArray()); - eventData = new DynamicEventData(eventName, data); + var rawBytes = message.Body.ToArray(); + eventData = new DynamicEventData(eventName, Serializer.Deserialize(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(incomingEvent.EventData); - eventData = new DynamicEventData(incomingEvent.EventName, element); + eventData = new DynamicEventData(incomingEvent.EventName, Serializer.Deserialize(incomingEvent.EventData)); eventType = typeof(DynamicEventData); } else diff --git a/framework/src/Volo.Abp.EventBus.Dapr/Volo/Abp/EventBus/Dapr/DaprDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Dapr/Volo/Abp/EventBus/Dapr/DaprDistributedEventBus.cs index e2d4ca5648..37e34d864b 100644 --- a/framework/src/Volo.Abp.EventBus.Dapr/Volo/Abp/EventBus/Dapr/DaprDistributedEventBus.cs +++ b/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)) diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs index 50b59fc28b..ff7f9f6816 100644 --- a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs +++ b/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(message.Value); - eventData = new DynamicEventData(eventName, element); + eventData = new DynamicEventData(eventName, Serializer.Deserialize(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(incomingEvent.EventData); - eventData = new DynamicEventData(incomingEvent.EventName, element); + eventData = new DynamicEventData(incomingEvent.EventName, Serializer.Deserialize(incomingEvent.EventData)); eventType = typeof(DynamicEventData); } else diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs index 98710f7487..747c0ef4b3 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs +++ b/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(ea.Body.ToArray())); + eventData = new DynamicEventData(eventName, Serializer.Deserialize(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)) diff --git a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs index d382585c6d..52c0de77c9 100644 --- a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs +++ b/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)) diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs index 2bec5815e9..c4d186e2b3 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs +++ b/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); diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus.cs index f92d453a82..f675f71b58 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus.cs +++ b/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(outgoingEvent.EventData)!); + System.Text.Json.JsonSerializer.Deserialize(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(incomingEvent.EventData)!); + System.Text.Json.JsonSerializer.Deserialize(incomingEvent.EventData)!); } else { - eventData = JsonSerializer.Deserialize(incomingEvent.EventData, eventType)!; + eventData = System.Text.Json.JsonSerializer.Deserialize(incomingEvent.EventData, eventType)!; } var exceptions = new List(); @@ -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) diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs index 6a8e357373..41fe39fb3f 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs +++ b/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 exceptions) { if (exceptions.Count == 1) diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventBus.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventBus.cs index d56a81fb39..5d8947dc40 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Local/LocalEventBus.cs +++ b/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); diff --git a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus_Test.cs b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus_Test.cs index aa16a5542d..d6f4c2275c 100644 --- a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus_Test.cs +++ b/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(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(async (d) => + { + countA++; + await Task.CompletedTask; + }))); + + using var subB = DistributedEventBus.Subscribe(eventNameB, + new SingleInstanceHandlerFactory(new ActionEventHandler(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(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 + { + private readonly Action _onHandle; + + public TestDynamicDistributedEventHandler(Action onHandle) + { + _onHandle = onHandle; + } + + public Task HandleEventAsync(DynamicEventData eventData) + { + _onHandle(); + return Task.CompletedTask; + } + } } diff --git a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Local/LocalEventBus_Dynamic_Test.cs b/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Local/LocalEventBus_Dynamic_Test.cs index 1b0e7790a9..c9d5180186 100644 --- a/framework/test/Volo.Abp.EventBus.Tests/Volo/Abp/EventBus/Local/LocalEventBus_Dynamic_Test.cs +++ b/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(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>(); - 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(); + + using var subscription = LocalEventBus.Subscribe(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(async (d) => + { + handleCount++; + await Task.CompletedTask; + }))); + + LocalEventBus.Subscribe(eventName, + new SingleInstanceHandlerFactory(new ActionEventHandler(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(async (d) => + { + countA++; + await Task.CompletedTask; + }))); + + using var subB = LocalEventBus.Subscribe(eventNameB, + new SingleInstanceHandlerFactory(new ActionEventHandler(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(async (d) => { - receivedData = d.ConvertToTypedObject(); + 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(); + + using var subscription = LocalEventBus.Subscribe(async (d) => + { + receivedData = d; + await Task.CompletedTask; + }); + + await LocalEventBus.PublishAsync(eventName, new { Value = 77 }); receivedData.ShouldNotBeNull(); - receivedData.Value.ShouldBe(99); + receivedData.Value.ShouldBe(77); } }