mirror of https://github.com/abpframework/abp.git
39 changed files with 1201 additions and 791 deletions
@ -1,65 +1,7 @@ |
|||||
using System; |
namespace Volo.Abp.EventBus.Distributed |
||||
using System.Threading.Tasks; |
|
||||
|
|
||||
namespace Volo.Abp.EventBus.Distributed |
|
||||
{ |
{ |
||||
public interface IDistributedEventBus |
public interface IDistributedEventBus : IEventBus |
||||
{ |
{ |
||||
/// <summary>
|
|
||||
/// Subscribes to an event.
|
|
||||
/// Given action is called for all event occurrences.
|
|
||||
/// </summary>
|
|
||||
/// <param name="action">Action to handle events</param>
|
|
||||
/// <typeparam name="TEvent">Event type</typeparam>
|
|
||||
IDisposable Subscribe<TEvent>(Func<TEvent, Task> action) |
|
||||
where TEvent : class; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Subscribes to an event.
|
|
||||
/// Same (given) instance of the handler is used for all event occurrences.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TEvent">Event type</typeparam>
|
|
||||
/// <param name="handler">Object to handle the event</param>
|
|
||||
IDisposable Subscribe<TEvent>(IEventHandler<TEvent> handler) |
|
||||
where TEvent : class; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Subscribes to an event.
|
|
||||
/// A new instance of <see cref="THandler"/> object is created for every event occurrence.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TEvent">Event type</typeparam>
|
|
||||
/// <typeparam name="THandler">Type of the event handler</typeparam>
|
|
||||
IDisposable Subscribe<TEvent, THandler>() |
|
||||
where TEvent : class |
|
||||
where THandler : IEventHandler, new(); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Subscribes to an event.
|
|
||||
/// Same (given) instance of the handler is used for all event occurrences.
|
|
||||
/// </summary>
|
|
||||
/// <param name="eventType">Event type</param>
|
|
||||
/// <param name="handler">Object to handle the event</param>
|
|
||||
IDisposable Subscribe(Type eventType, IEventHandler handler); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Subscribes to an event.
|
|
||||
/// Given factory is used to create/release handlers
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TEvent">Event type</typeparam>
|
|
||||
/// <param name="factory">A factory to create/release handlers</param>
|
|
||||
IDisposable Subscribe<TEvent>(IEventHandlerFactory factory) |
|
||||
where TEvent : class; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Subscribes to an event.
|
|
||||
/// </summary>
|
|
||||
/// <param name="eventType">Event type</param>
|
|
||||
/// <param name="factory">A factory to create/release handlers</param>
|
|
||||
IDisposable Subscribe(Type eventType, IEventHandlerFactory factory); |
|
||||
|
|
||||
Task PublishAsync<TEvent>(TEvent eventData) |
|
||||
where TEvent : class; |
|
||||
|
|
||||
Task PublishAsync(Type eventType, object eventData); |
|
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -1,58 +1,95 @@ |
|||||
using System; |
using System; |
||||
using System.Threading.Tasks; |
using System.Threading.Tasks; |
||||
using Volo.Abp.DependencyInjection; |
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.EventBus.Local; |
||||
|
|
||||
namespace Volo.Abp.EventBus.Distributed |
namespace Volo.Abp.EventBus.Distributed |
||||
{ |
{ |
||||
[Dependency(TryRegister = true)] |
[Dependency(TryRegister = true)] |
||||
|
[ExposeServices(typeof(IDistributedEventBus), typeof(LocalDistributedEventBus))] |
||||
public class LocalDistributedEventBus : IDistributedEventBus, ITransientDependency |
public class LocalDistributedEventBus : IDistributedEventBus, ITransientDependency |
||||
{ |
{ |
||||
private readonly IEventBus _eventBus; |
private readonly ILocalEventBus _localEventBus; |
||||
|
|
||||
public LocalDistributedEventBus(IEventBus eventBus) |
public LocalDistributedEventBus(ILocalEventBus localEventBus) |
||||
{ |
{ |
||||
_eventBus = eventBus; |
_localEventBus = localEventBus; |
||||
} |
} |
||||
|
|
||||
public IDisposable Subscribe<TEvent>(Func<TEvent, Task> action) where TEvent : class |
public IDisposable Subscribe<TEvent>(Func<TEvent, Task> action) where TEvent : class |
||||
{ |
{ |
||||
return _eventBus.Register(action); |
return _localEventBus.Subscribe(action); |
||||
} |
} |
||||
|
|
||||
public IDisposable Subscribe<TEvent>(IEventHandler<TEvent> handler) where TEvent : class |
public IDisposable Subscribe<TEvent>(IEventHandler<TEvent> handler) where TEvent : class |
||||
{ |
{ |
||||
return _eventBus.Register(handler); |
return _localEventBus.Subscribe(handler); |
||||
} |
} |
||||
|
|
||||
public IDisposable Subscribe<TEvent, THandler>() where TEvent : class where THandler : IEventHandler, new() |
public IDisposable Subscribe<TEvent, THandler>() where TEvent : class where THandler : IEventHandler, new() |
||||
{ |
{ |
||||
return _eventBus.Register<TEvent, THandler>(); |
return _localEventBus.Subscribe<TEvent, THandler>(); |
||||
} |
} |
||||
|
|
||||
public IDisposable Subscribe(Type eventType, IEventHandler handler) |
public IDisposable Subscribe(Type eventType, IEventHandler handler) |
||||
{ |
{ |
||||
return _eventBus.Register(eventType, handler); |
return _localEventBus.Subscribe(eventType, handler); |
||||
} |
} |
||||
|
|
||||
public IDisposable Subscribe<TEvent>(IEventHandlerFactory factory) where TEvent : class |
public IDisposable Subscribe<TEvent>(IEventHandlerFactory factory) where TEvent : class |
||||
{ |
{ |
||||
return _eventBus.Register<TEvent>(factory); |
return _localEventBus.Subscribe<TEvent>(factory); |
||||
} |
} |
||||
|
|
||||
public IDisposable Subscribe(Type eventType, IEventHandlerFactory factory) |
public IDisposable Subscribe(Type eventType, IEventHandlerFactory factory) |
||||
{ |
{ |
||||
return _eventBus.Register(eventType, factory); |
return _localEventBus.Subscribe(eventType, factory); |
||||
|
} |
||||
|
|
||||
|
public void Unsubscribe<TEvent>(Func<TEvent, Task> action) where TEvent : class |
||||
|
{ |
||||
|
_localEventBus.Unsubscribe(action); |
||||
|
} |
||||
|
|
||||
|
public void Unsubscribe<TEvent>(IEventHandler<TEvent> handler) where TEvent : class |
||||
|
{ |
||||
|
_localEventBus.Unsubscribe(handler); |
||||
|
} |
||||
|
|
||||
|
public void Unsubscribe(Type eventType, IEventHandler handler) |
||||
|
{ |
||||
|
_localEventBus.Unsubscribe(eventType, handler); |
||||
|
} |
||||
|
|
||||
|
public void Unsubscribe<TEvent>(IEventHandlerFactory factory) where TEvent : class |
||||
|
{ |
||||
|
_localEventBus.Unsubscribe<TEvent>(factory); |
||||
|
} |
||||
|
|
||||
|
public void Unsubscribe(Type eventType, IEventHandlerFactory factory) |
||||
|
{ |
||||
|
_localEventBus.Unsubscribe(eventType, factory); |
||||
|
} |
||||
|
|
||||
|
public void UnsubscribeAll<TEvent>() where TEvent : class |
||||
|
{ |
||||
|
_localEventBus.UnsubscribeAll<TEvent>(); |
||||
|
} |
||||
|
|
||||
|
public void UnsubscribeAll(Type eventType) |
||||
|
{ |
||||
|
_localEventBus.UnsubscribeAll(eventType); |
||||
} |
} |
||||
|
|
||||
public Task PublishAsync<TEvent>(TEvent eventData) |
public Task PublishAsync<TEvent>(TEvent eventData) |
||||
where TEvent : class |
where TEvent : class |
||||
{ |
{ |
||||
return _eventBus.TriggerAsync(eventData); |
return _localEventBus.PublishAsync(eventData); |
||||
} |
} |
||||
|
|
||||
public Task PublishAsync(Type eventType, object eventData) |
public Task PublishAsync(Type eventType, object eventData) |
||||
{ |
{ |
||||
return _eventBus.TriggerAsync(eventType, eventData); |
return _localEventBus.PublishAsync(eventType, eventData); |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using Volo.Abp.Collections; |
||||
|
|
||||
|
namespace Volo.Abp.EventBus.Distributed |
||||
|
{ |
||||
|
public class DistributedEventBusOptions |
||||
|
{ |
||||
|
public ITypeList<IEventHandler> Handlers { get; } |
||||
|
|
||||
|
public DistributedEventBusOptions() |
||||
|
{ |
||||
|
Handlers = new TypeList<IEventHandler>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,335 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Concurrent; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
using System.Reflection; |
|
||||
using System.Runtime.CompilerServices; |
|
||||
using System.Threading; |
|
||||
using System.Threading.Tasks; |
|
||||
using Microsoft.Extensions.Logging; |
|
||||
using Microsoft.Extensions.Logging.Abstractions; |
|
||||
using Microsoft.Extensions.Options; |
|
||||
using Volo.Abp.DependencyInjection; |
|
||||
using Volo.Abp.Threading; |
|
||||
|
|
||||
namespace Volo.Abp.EventBus |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Implements EventBus as Singleton pattern.
|
|
||||
/// </summary>
|
|
||||
public class EventBus : IEventBus, ISingletonDependency |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Reference to the Logger.
|
|
||||
/// </summary>
|
|
||||
public ILogger<EventBus> Logger { get; set; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// All registered handler factories.
|
|
||||
/// Key: Type of the event
|
|
||||
/// Value: List of handler factories
|
|
||||
/// </summary>
|
|
||||
protected ConcurrentDictionary<Type, List<IEventHandlerFactory>> HandlerFactories { get; } |
|
||||
|
|
||||
protected EventBusOptions Options { get; } |
|
||||
|
|
||||
public EventBus( |
|
||||
IOptions<EventBusOptions> options, |
|
||||
IServiceProvider serviceProvider) |
|
||||
{ |
|
||||
Options = options.Value; |
|
||||
Logger = NullLogger<EventBus>.Instance; |
|
||||
HandlerFactories = new ConcurrentDictionary<Type, List<IEventHandlerFactory>>(); |
|
||||
|
|
||||
RegisterHandlersInOptions(serviceProvider); |
|
||||
} |
|
||||
|
|
||||
protected virtual void RegisterHandlersInOptions(IServiceProvider serviceProvider) |
|
||||
{ |
|
||||
foreach (var handler in Options.Handlers) |
|
||||
{ |
|
||||
var interfaces = handler.GetInterfaces(); |
|
||||
foreach (var @interface in interfaces) |
|
||||
{ |
|
||||
if (!typeof(IEventHandler).GetTypeInfo().IsAssignableFrom(@interface)) |
|
||||
{ |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
var genericArgs = @interface.GetGenericArguments(); |
|
||||
if (genericArgs.Length == 1) |
|
||||
{ |
|
||||
Register(genericArgs[0], new IocEventHandlerFactory(serviceProvider, handler)); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public IDisposable Register<TEvent>(Func<TEvent, Task> action) where TEvent : class |
|
||||
{ |
|
||||
return Register(typeof(TEvent), new ActionEventHandler<TEvent>(action)); |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public IDisposable Register<TEvent>(IEventHandler<TEvent> handler) where TEvent : class |
|
||||
{ |
|
||||
return Register(typeof(TEvent), handler); |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public IDisposable Register<TEvent, THandler>() |
|
||||
where TEvent : class |
|
||||
where THandler : IEventHandler, new() |
|
||||
{ |
|
||||
return Register(typeof(TEvent), new TransientEventHandlerFactory<THandler>()); |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public IDisposable Register(Type eventType, IEventHandler handler) |
|
||||
{ |
|
||||
return Register(eventType, new SingleInstanceHandlerFactory(handler)); |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public IDisposable Register<TEvent>(IEventHandlerFactory factory) where TEvent : class |
|
||||
{ |
|
||||
return Register(typeof(TEvent), factory); |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public IDisposable Register(Type eventType, IEventHandlerFactory factory) |
|
||||
{ |
|
||||
GetOrCreateHandlerFactories(eventType) |
|
||||
.Locking(factories => factories.Add(factory)); |
|
||||
|
|
||||
return new EventHandlerFactoryUnregistrar(this, eventType, factory); |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public void Unregister<TEvent>(Func<TEvent, Task> action) where TEvent : class |
|
||||
{ |
|
||||
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; |
|
||||
}); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public void Unregister<TEvent>(IEventHandler<TEvent> handler) where TEvent : class |
|
||||
{ |
|
||||
Unregister(typeof(TEvent), handler); |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public void Unregister(Type eventType, IEventHandler handler) |
|
||||
{ |
|
||||
GetOrCreateHandlerFactories(eventType) |
|
||||
.Locking(factories => |
|
||||
{ |
|
||||
factories.RemoveAll( |
|
||||
factory => |
|
||||
factory is SingleInstanceHandlerFactory && |
|
||||
(factory as SingleInstanceHandlerFactory).HandlerInstance == handler |
|
||||
); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public void Unregister<TEvent>(IEventHandlerFactory factory) where TEvent : class |
|
||||
{ |
|
||||
Unregister(typeof(TEvent), factory); |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public void Unregister(Type eventType, IEventHandlerFactory factory) |
|
||||
{ |
|
||||
GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Remove(factory)); |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public void UnregisterAll<TEvent>() where TEvent : class |
|
||||
{ |
|
||||
UnregisterAll(typeof(TEvent)); |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public void UnregisterAll(Type eventType) |
|
||||
{ |
|
||||
GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Clear()); |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public Task TriggerAsync<TEvent>(TEvent eventData) where TEvent : class |
|
||||
{ |
|
||||
return TriggerAsync(typeof(TEvent), eventData); |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
public async Task TriggerAsync(Type eventType, object eventData) |
|
||||
{ |
|
||||
var exceptions = new List<Exception>(); |
|
||||
|
|
||||
await new SynchronizationContextRemover(); |
|
||||
|
|
||||
foreach (var handlerFactories in GetHandlerFactories(eventType)) |
|
||||
{ |
|
||||
foreach (var handlerFactory in handlerFactories.EventHandlerFactories) |
|
||||
{ |
|
||||
await TriggerAsyncHandlingException(handlerFactory, handlerFactories.EventType, eventData, exceptions); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
//Implements generic argument inheritance. See classWithInheritableGenericArgument
|
|
||||
if (eventType.GetTypeInfo().IsGenericType && |
|
||||
eventType.GetGenericArguments().Length == 1 && |
|
||||
typeof(IEventDataWithInheritableGenericArgument).IsAssignableFrom(eventType)) |
|
||||
{ |
|
||||
var genericArg = eventType.GetGenericArguments()[0]; |
|
||||
var baseArg = genericArg.GetTypeInfo().BaseType; |
|
||||
if (baseArg != null) |
|
||||
{ |
|
||||
var baseEventType = eventType.GetGenericTypeDefinition().MakeGenericType(baseArg); |
|
||||
var constructorArgs = ((IEventDataWithInheritableGenericArgument)eventData).GetConstructorArgs(); |
|
||||
var baseEventData = Activator.CreateInstance(baseEventType, constructorArgs); |
|
||||
await TriggerAsync(baseEventType, baseEventData); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (exceptions.Any()) |
|
||||
{ |
|
||||
if (exceptions.Count == 1) |
|
||||
{ |
|
||||
exceptions[0].ReThrow(); |
|
||||
} |
|
||||
|
|
||||
throw new AggregateException("More than one error has occurred while triggering the event: " + eventType, exceptions); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private async Task TriggerAsyncHandlingException(IEventHandlerFactory asyncHandlerFactory, Type eventType, object eventData, List<Exception> exceptions) |
|
||||
{ |
|
||||
using (var eventHandlerWrapper = asyncHandlerFactory.GetHandler()) |
|
||||
{ |
|
||||
try |
|
||||
{ |
|
||||
var asyncHandlerType = typeof(IEventHandler<>).MakeGenericType(eventType); |
|
||||
|
|
||||
var method = asyncHandlerType.GetMethod( |
|
||||
"HandleEventAsync", |
|
||||
new[] { eventType } |
|
||||
); |
|
||||
|
|
||||
await (Task)method.Invoke(eventHandlerWrapper.EventHandler, new[] { eventData }); |
|
||||
} |
|
||||
catch (TargetInvocationException ex) |
|
||||
{ |
|
||||
exceptions.Add(ex.InnerException); |
|
||||
} |
|
||||
catch (Exception ex) |
|
||||
{ |
|
||||
exceptions.Add(ex); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private 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(); |
|
||||
} |
|
||||
|
|
||||
private static bool ShouldTriggerEventForHandler(Type eventType, Type handlerType) |
|
||||
{ |
|
||||
//Should trigger same type
|
|
||||
if (handlerType == eventType) |
|
||||
{ |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
//Should trigger for inherited types
|
|
||||
if (handlerType.IsAssignableFrom(eventType)) |
|
||||
{ |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
private List<IEventHandlerFactory> GetOrCreateHandlerFactories(Type eventType) |
|
||||
{ |
|
||||
return HandlerFactories.GetOrAdd(eventType, (type) => new List<IEventHandlerFactory>()); |
|
||||
} |
|
||||
|
|
||||
private class EventTypeWithEventHandlerFactories |
|
||||
{ |
|
||||
public Type EventType { get; } |
|
||||
|
|
||||
public List<IEventHandlerFactory> EventHandlerFactories { get; } |
|
||||
|
|
||||
public EventTypeWithEventHandlerFactories(Type eventType, List<IEventHandlerFactory> eventHandlerFactories) |
|
||||
{ |
|
||||
EventType = eventType; |
|
||||
EventHandlerFactories = eventHandlerFactories; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Reference from
|
|
||||
// https://blogs.msdn.microsoft.com/benwilli/2017/02/09/an-alternative-to-configureawaitfalse-everywhere/
|
|
||||
private struct SynchronizationContextRemover : INotifyCompletion |
|
||||
{ |
|
||||
public bool IsCompleted |
|
||||
{ |
|
||||
get { return SynchronizationContext.Current == null; } |
|
||||
} |
|
||||
|
|
||||
public void OnCompleted(Action continuation) |
|
||||
{ |
|
||||
var prevContext = SynchronizationContext.Current; |
|
||||
try |
|
||||
{ |
|
||||
SynchronizationContext.SetSynchronizationContext(null); |
|
||||
continuation(); |
|
||||
} |
|
||||
finally |
|
||||
{ |
|
||||
SynchronizationContext.SetSynchronizationContext(prevContext); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public SynchronizationContextRemover GetAwaiter() |
|
||||
{ |
|
||||
return this; |
|
||||
} |
|
||||
|
|
||||
public void GetResult() |
|
||||
{ |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,225 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Reflection; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.EventBus.Distributed; |
||||
|
using Volo.Abp.Reflection; |
||||
|
|
||||
|
namespace Volo.Abp.EventBus.Local |
||||
|
{ |
||||
|
public abstract class EventBusBase : IEventBus |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public virtual IDisposable Subscribe<TEvent>(Func<TEvent, Task> action) where TEvent : class |
||||
|
{ |
||||
|
return Subscribe(typeof(TEvent), new ActionEventHandler<TEvent>(action)); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public virtual IDisposable Subscribe<TEvent>(IEventHandler<TEvent> handler) where TEvent : class |
||||
|
{ |
||||
|
return Subscribe(typeof(TEvent), handler); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public virtual IDisposable Subscribe<TEvent, THandler>() |
||||
|
where TEvent : class |
||||
|
where THandler : IEventHandler, new() |
||||
|
{ |
||||
|
return Subscribe(typeof(TEvent), new TransientEventHandlerFactory<THandler>()); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public virtual IDisposable Subscribe(Type eventType, IEventHandler handler) |
||||
|
{ |
||||
|
return Subscribe(eventType, new SingleInstanceHandlerFactory(handler)); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public virtual IDisposable Subscribe<TEvent>(IEventHandlerFactory factory) where TEvent : class |
||||
|
{ |
||||
|
return Subscribe(typeof(TEvent), factory); |
||||
|
} |
||||
|
|
||||
|
public abstract IDisposable Subscribe(Type eventType, IEventHandlerFactory factory); |
||||
|
|
||||
|
public abstract void Unsubscribe<TEvent>(Func<TEvent, Task> action) where TEvent : class; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public virtual void Unsubscribe<TEvent>(IEventHandler<TEvent> handler) where TEvent : class |
||||
|
{ |
||||
|
Unsubscribe(typeof(TEvent), handler); |
||||
|
} |
||||
|
|
||||
|
public abstract void Unsubscribe(Type eventType, IEventHandler handler); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public virtual void Unsubscribe<TEvent>(IEventHandlerFactory factory) where TEvent : class |
||||
|
{ |
||||
|
Unsubscribe(typeof(TEvent), factory); |
||||
|
} |
||||
|
|
||||
|
public abstract void Unsubscribe(Type eventType, IEventHandlerFactory factory); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public virtual void UnsubscribeAll<TEvent>() where TEvent : class |
||||
|
{ |
||||
|
UnsubscribeAll(typeof(TEvent)); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public abstract void UnsubscribeAll(Type eventType); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public virtual Task PublishAsync<TEvent>(TEvent eventData) where TEvent : class |
||||
|
{ |
||||
|
return PublishAsync(typeof(TEvent), eventData); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public abstract Task PublishAsync(Type eventType, object eventData); |
||||
|
|
||||
|
public virtual async Task TriggerHandlersAsync(Type eventType, object eventData) |
||||
|
{ |
||||
|
var exceptions = new List<Exception>(); |
||||
|
|
||||
|
await TriggerHandlersAsync(eventType, eventData, exceptions); |
||||
|
|
||||
|
if (exceptions.Any()) |
||||
|
{ |
||||
|
if (exceptions.Count == 1) |
||||
|
{ |
||||
|
exceptions[0].ReThrow(); |
||||
|
} |
||||
|
|
||||
|
throw new AggregateException("More than one error has occurred while triggering the event: " + eventType, exceptions); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task TriggerHandlersAsync(Type eventType, object eventData, List<Exception> exceptions) |
||||
|
{ |
||||
|
await new SynchronizationContextRemover(); |
||||
|
|
||||
|
foreach (var handlerFactories in GetHandlerFactories(eventType)) |
||||
|
{ |
||||
|
foreach (var handlerFactory in handlerFactories.EventHandlerFactories) |
||||
|
{ |
||||
|
await TriggerHandlerAsync(handlerFactory, handlerFactories.EventType, eventData, exceptions); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//Implements generic argument inheritance. See IEventDataWithInheritableGenericArgument
|
||||
|
if (eventType.GetTypeInfo().IsGenericType && |
||||
|
eventType.GetGenericArguments().Length == 1 && |
||||
|
typeof(IEventDataWithInheritableGenericArgument).IsAssignableFrom(eventType)) |
||||
|
{ |
||||
|
var genericArg = eventType.GetGenericArguments()[0]; |
||||
|
var baseArg = genericArg.GetTypeInfo().BaseType; |
||||
|
if (baseArg != null) |
||||
|
{ |
||||
|
var baseEventType = eventType.GetGenericTypeDefinition().MakeGenericType(baseArg); |
||||
|
var constructorArgs = ((IEventDataWithInheritableGenericArgument)eventData).GetConstructorArgs(); |
||||
|
var baseEventData = Activator.CreateInstance(baseEventType, constructorArgs); |
||||
|
await PublishAsync(baseEventType, baseEventData); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected abstract IEnumerable<EventTypeWithEventHandlerFactories> GetHandlerFactories(Type eventType); |
||||
|
|
||||
|
protected virtual async Task TriggerHandlerAsync(IEventHandlerFactory asyncHandlerFactory, Type eventType, object eventData, List<Exception> exceptions) |
||||
|
{ |
||||
|
using (var eventHandlerWrapper = asyncHandlerFactory.GetHandler()) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var handlerType = eventHandlerWrapper.EventHandler.GetType(); |
||||
|
|
||||
|
if (ReflectionHelper.IsAssignableToGenericType(handlerType, typeof(IEventHandler<>))) |
||||
|
{ |
||||
|
var method = typeof(IEventHandler<>) //TODO: to a static field
|
||||
|
.MakeGenericType(eventType) |
||||
|
.GetMethod( |
||||
|
nameof(IEventHandler<object>.HandleEventAsync), |
||||
|
new[] { eventType } |
||||
|
); |
||||
|
|
||||
|
await (Task)method.Invoke(eventHandlerWrapper.EventHandler, new[] { eventData }); |
||||
|
} |
||||
|
else if (ReflectionHelper.IsAssignableToGenericType(handlerType, typeof(IDistributedEventHandler<>))) |
||||
|
{ |
||||
|
var method = typeof(IDistributedEventHandler<>) //TODO: to a static field
|
||||
|
.MakeGenericType(eventType) |
||||
|
.GetMethod( |
||||
|
nameof(IDistributedEventHandler<object>.HandleEventAsync), |
||||
|
new[] { eventType } |
||||
|
); |
||||
|
|
||||
|
await (Task)method.Invoke(eventHandlerWrapper.EventHandler, new[] { eventData }); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
throw new AbpException("The object instance is not an event handler. Object type: " + handlerType.AssemblyQualifiedName); |
||||
|
} |
||||
|
} |
||||
|
catch (TargetInvocationException ex) |
||||
|
{ |
||||
|
exceptions.Add(ex.InnerException); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
exceptions.Add(ex); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected class EventTypeWithEventHandlerFactories |
||||
|
{ |
||||
|
public Type EventType { get; } |
||||
|
|
||||
|
public List<IEventHandlerFactory> EventHandlerFactories { get; } |
||||
|
|
||||
|
public EventTypeWithEventHandlerFactories(Type eventType, List<IEventHandlerFactory> eventHandlerFactories) |
||||
|
{ |
||||
|
EventType = eventType; |
||||
|
EventHandlerFactories = eventHandlerFactories; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Reference from
|
||||
|
// https://blogs.msdn.microsoft.com/benwilli/2017/02/09/an-alternative-to-configureawaitfalse-everywhere/
|
||||
|
protected struct SynchronizationContextRemover : INotifyCompletion |
||||
|
{ |
||||
|
public bool IsCompleted |
||||
|
{ |
||||
|
get { return SynchronizationContext.Current == null; } |
||||
|
} |
||||
|
|
||||
|
public void OnCompleted(Action continuation) |
||||
|
{ |
||||
|
var prevContext = SynchronizationContext.Current; |
||||
|
try |
||||
|
{ |
||||
|
SynchronizationContext.SetSynchronizationContext(null); |
||||
|
continuation(); |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
SynchronizationContext.SetSynchronizationContext(prevContext); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public SynchronizationContextRemover GetAwaiter() |
||||
|
{ |
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public void GetResult() |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using JetBrains.Annotations; |
||||
|
|
||||
|
namespace Volo.Abp.EventBus |
||||
|
{ |
||||
|
[AttributeUsage(AttributeTargets.Class)] |
||||
|
public class EventNameAttribute : Attribute, IEventNameProvider |
||||
|
{ |
||||
|
public string Name { get; } |
||||
|
|
||||
|
public EventNameAttribute([NotNull] string name) |
||||
|
{ |
||||
|
Name = Check.NotNullOrWhiteSpace(name, nameof(name)); |
||||
|
} |
||||
|
|
||||
|
public static string GetName<TEvent>() |
||||
|
{ |
||||
|
return GetName(typeof(TEvent)); |
||||
|
} |
||||
|
|
||||
|
public static string GetName([NotNull] Type eventType) |
||||
|
{ |
||||
|
Check.NotNull(eventType, nameof(eventType)); |
||||
|
|
||||
|
return eventType |
||||
|
.GetCustomAttributes(true) |
||||
|
.OfType<IEventNameProvider>() |
||||
|
.FirstOrDefault() |
||||
|
?.Name |
||||
|
?? eventType.FullName; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public interface IEventNameProvider |
||||
|
{ |
||||
|
string Name { get; } |
||||
|
} |
||||
|
} |
||||
@ -1,131 +1,7 @@ |
|||||
using System; |
|
||||
using System.Threading.Tasks; |
|
||||
|
|
||||
namespace Volo.Abp.EventBus |
namespace Volo.Abp.EventBus |
||||
{ |
{ |
||||
/// <summary>
|
public interface IEventBus : IEventSubscriber, IEventPublisher |
||||
/// Defines interface of the event bus.
|
|
||||
/// </summary>
|
|
||||
public interface IEventBus |
|
||||
{ |
{ |
||||
/// <summary>
|
|
||||
/// Registers to an event.
|
|
||||
/// Given action is called for all event occurrences.
|
|
||||
/// </summary>
|
|
||||
/// <param name="action">Action to handle events</param>
|
|
||||
/// <typeparam name="TEvent">Event type</typeparam>
|
|
||||
IDisposable Register<TEvent>(Func<TEvent, Task> action) |
|
||||
where TEvent : class; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Registers to an event.
|
|
||||
/// Same (given) instance of the handler is used for all event occurrences.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TEvent">Event type</typeparam>
|
|
||||
/// <param name="handler">Object to handle the event</param>
|
|
||||
IDisposable Register<TEvent>(IEventHandler<TEvent> handler) |
|
||||
where TEvent : class; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Registers to an event.
|
|
||||
/// A new instance of <see cref="THandler"/> object is created for every event occurrence.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TEvent">Event type</typeparam>
|
|
||||
/// <typeparam name="THandler">Type of the event handler</typeparam>
|
|
||||
IDisposable Register<TEvent, THandler>() |
|
||||
where TEvent : class |
|
||||
where THandler : IEventHandler, new(); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Registers to an event.
|
|
||||
/// Same (given) instance of the handler is used for all event occurrences.
|
|
||||
/// </summary>
|
|
||||
/// <param name="eventType">Event type</param>
|
|
||||
/// <param name="handler">Object to handle the event</param>
|
|
||||
IDisposable Register(Type eventType, IEventHandler handler); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Registers to an event.
|
|
||||
/// Given factory is used to create/release handlers
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TEvent">Event type</typeparam>
|
|
||||
/// <param name="factory">A factory to create/release handlers</param>
|
|
||||
IDisposable Register<TEvent>(IEventHandlerFactory factory) |
|
||||
where TEvent : class; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Registers to an event.
|
|
||||
/// </summary>
|
|
||||
/// <param name="eventType">Event type</param>
|
|
||||
/// <param name="factory">A factory to create/release handlers</param>
|
|
||||
IDisposable Register(Type eventType, IEventHandlerFactory factory); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Unregisters from an event.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TEvent">Event type</typeparam>
|
|
||||
/// <param name="action"></param>
|
|
||||
void Unregister<TEvent>(Func<TEvent, Task> action) |
|
||||
where TEvent : class; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Unregisters from an event.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TEvent">Event type</typeparam>
|
|
||||
/// <param name="handler">Handler object that is registered before</param>
|
|
||||
void Unregister<TEvent>(IEventHandler<TEvent> handler) |
|
||||
where TEvent : class; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Unregisters from an event.
|
|
||||
/// </summary>
|
|
||||
/// <param name="eventType">Event type</param>
|
|
||||
/// <param name="handler">Handler object that is registered before</param>
|
|
||||
void Unregister(Type eventType, IEventHandler handler); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Unregisters from an event.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TEvent">Event type</typeparam>
|
|
||||
/// <param name="factory">Factory object that is registered before</param>
|
|
||||
void Unregister<TEvent>(IEventHandlerFactory factory) |
|
||||
where TEvent : class; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Unregisters from an event.
|
|
||||
/// </summary>
|
|
||||
/// <param name="eventType">Event type</param>
|
|
||||
/// <param name="factory">Factory object that is registered before</param>
|
|
||||
void Unregister(Type eventType, IEventHandlerFactory factory); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Unregisters all event handlers of given event type.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TEvent">Event type</typeparam>
|
|
||||
void UnregisterAll<TEvent>() |
|
||||
where TEvent : class; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Unregisters all event handlers of given event type.
|
|
||||
/// </summary>
|
|
||||
/// <param name="eventType">Event type</param>
|
|
||||
void UnregisterAll(Type eventType); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Triggers an event asynchronously.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TEvent">Event type</typeparam>
|
|
||||
/// <param name="eventData">Related data for the event</param>
|
|
||||
/// <returns>The task to handle async operation</returns>
|
|
||||
Task TriggerAsync<TEvent>(TEvent eventData) |
|
||||
where TEvent : class; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Triggers an event asynchronously.
|
|
||||
/// </summary>
|
|
||||
/// <param name="eventType">Event type</param>
|
|
||||
/// <param name="eventData">Related data for the event</param>
|
|
||||
/// <returns>The task to handle async operation</returns>
|
|
||||
Task TriggerAsync(Type eventType, object eventData); |
|
||||
} |
} |
||||
} |
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.EventBus |
||||
|
{ |
||||
|
public interface IEventPublisher |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Triggers an event asynchronously.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TEvent">Event type</typeparam>
|
||||
|
/// <param name="eventData">Related data for the event</param>
|
||||
|
/// <returns>The task to handle async operation</returns>
|
||||
|
Task PublishAsync<TEvent>(TEvent eventData) |
||||
|
where TEvent : class; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Triggers an event asynchronously.
|
||||
|
/// </summary>
|
||||
|
/// <param name="eventType">Event type</param>
|
||||
|
/// <param name="eventData">Related data for the event</param>
|
||||
|
/// <returns>The task to handle async operation</returns>
|
||||
|
Task PublishAsync(Type eventType, object eventData); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,111 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.EventBus |
||||
|
{ |
||||
|
public interface IEventSubscriber |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Registers to an event.
|
||||
|
/// Given action is called for all event occurrences.
|
||||
|
/// </summary>
|
||||
|
/// <param name="action">Action to handle events</param>
|
||||
|
/// <typeparam name="TEvent">Event type</typeparam>
|
||||
|
IDisposable Subscribe<TEvent>(Func<TEvent, Task> action) |
||||
|
where TEvent : class; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Registers to an event.
|
||||
|
/// Same (given) instance of the handler is used for all event occurrences.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TEvent">Event type</typeparam>
|
||||
|
/// <param name="handler">Object to handle the event</param>
|
||||
|
IDisposable Subscribe<TEvent>(IEventHandler<TEvent> handler) |
||||
|
where TEvent : class; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Registers to an event.
|
||||
|
/// A new instance of <see cref="THandler"/> object is created for every event occurrence.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TEvent">Event type</typeparam>
|
||||
|
/// <typeparam name="THandler">Type of the event handler</typeparam>
|
||||
|
IDisposable Subscribe<TEvent, THandler>() |
||||
|
where TEvent : class |
||||
|
where THandler : IEventHandler, new(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Registers to an event.
|
||||
|
/// Same (given) instance of the handler is used for all event occurrences.
|
||||
|
/// </summary>
|
||||
|
/// <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.
|
||||
|
/// Given factory is used to create/release handlers
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TEvent">Event type</typeparam>
|
||||
|
/// <param name="factory">A factory to create/release handlers</param>
|
||||
|
IDisposable Subscribe<TEvent>(IEventHandlerFactory factory) |
||||
|
where TEvent : class; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Registers to an event.
|
||||
|
/// </summary>
|
||||
|
/// <param name="eventType">Event type</param>
|
||||
|
/// <param name="factory">A factory to create/release handlers</param>
|
||||
|
IDisposable Subscribe(Type eventType, IEventHandlerFactory factory); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Unregisters from an event.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TEvent">Event type</typeparam>
|
||||
|
/// <param name="action"></param>
|
||||
|
void Unsubscribe<TEvent>(Func<TEvent, Task> action) |
||||
|
where TEvent : class; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Unregisters from an event.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TEvent">Event type</typeparam>
|
||||
|
/// <param name="handler">Handler object that is registered before</param>
|
||||
|
void Unsubscribe<TEvent>(IEventHandler<TEvent> handler) |
||||
|
where TEvent : class; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Unregisters from an event.
|
||||
|
/// </summary>
|
||||
|
/// <param name="eventType">Event type</param>
|
||||
|
/// <param name="handler">Handler object that is registered before</param>
|
||||
|
void Unsubscribe(Type eventType, IEventHandler handler); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Unregisters from an event.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TEvent">Event type</typeparam>
|
||||
|
/// <param name="factory">Factory object that is registered before</param>
|
||||
|
void Unsubscribe<TEvent>(IEventHandlerFactory factory) |
||||
|
where TEvent : class; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Unregisters from an event.
|
||||
|
/// </summary>
|
||||
|
/// <param name="eventType">Event type</param>
|
||||
|
/// <param name="factory">Factory object that is registered before</param>
|
||||
|
void Unsubscribe(Type eventType, IEventHandlerFactory factory); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Unregisters all event handlers of given event type.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TEvent">Event type</typeparam>
|
||||
|
void UnsubscribeAll<TEvent>() |
||||
|
where TEvent : class; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Unregisters all event handlers of given event type.
|
||||
|
/// </summary>
|
||||
|
/// <param name="eventType">Event type</param>
|
||||
|
void UnsubscribeAll(Type eventType); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
namespace Volo.Abp.EventBus.Local |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines interface of the event bus.
|
||||
|
/// </summary>
|
||||
|
public interface ILocalEventBus : IEventBus |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,180 @@ |
|||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Logging.Abstractions; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System; |
||||
|
using System.Collections.Concurrent; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Reflection; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Collections; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.Threading; |
||||
|
|
||||
|
namespace Volo.Abp.EventBus.Local |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Implements EventBus as Singleton pattern.
|
||||
|
/// </summary>
|
||||
|
[ExposeServices(typeof(ILocalEventBus), typeof(LocalEventBus))] |
||||
|
public class LocalEventBus : EventBusBase, ILocalEventBus, ISingletonDependency |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Reference to the Logger.
|
||||
|
/// </summary>
|
||||
|
public ILogger<LocalEventBus> Logger { get; set; } |
||||
|
|
||||
|
protected LocalEventBusOptions Options { get; } |
||||
|
|
||||
|
protected ConcurrentDictionary<Type, List<IEventHandlerFactory>> HandlerFactories { get; } |
||||
|
|
||||
|
protected IServiceProvider ServiceProvider { get; } |
||||
|
|
||||
|
public LocalEventBus( |
||||
|
IOptions<LocalEventBusOptions> options, |
||||
|
IServiceProvider serviceProvider) |
||||
|
{ |
||||
|
ServiceProvider = serviceProvider; |
||||
|
Options = options.Value; |
||||
|
Logger = NullLogger<LocalEventBus>.Instance; |
||||
|
|
||||
|
HandlerFactories = new ConcurrentDictionary<Type, List<IEventHandlerFactory>>(); |
||||
|
Subscribe(Options.Handlers); |
||||
|
} |
||||
|
|
||||
|
public virtual void Subscribe(ITypeList<IEventHandler> handlers) |
||||
|
{ |
||||
|
foreach (var handler in handlers) |
||||
|
{ |
||||
|
var interfaces = handler.GetInterfaces(); |
||||
|
foreach (var @interface in interfaces) |
||||
|
{ |
||||
|
if (!typeof(IEventHandler).GetTypeInfo().IsAssignableFrom(@interface)) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
var genericArgs = @interface.GetGenericArguments(); |
||||
|
if (genericArgs.Length == 1) |
||||
|
{ |
||||
|
Subscribe(genericArgs[0], new IocEventHandlerFactory(ServiceProvider, handler)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override IDisposable Subscribe(Type eventType, IEventHandlerFactory factory) |
||||
|
{ |
||||
|
GetOrCreateHandlerFactories(eventType) |
||||
|
.Locking(factories => factories.Add(factory)); |
||||
|
|
||||
|
return new EventHandlerFactoryUnregistrar(this, eventType, factory); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
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; |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public override void Unsubscribe(Type eventType, IEventHandler handler) |
||||
|
{ |
||||
|
GetOrCreateHandlerFactories(eventType) |
||||
|
.Locking(factories => |
||||
|
{ |
||||
|
factories.RemoveAll( |
||||
|
factory => |
||||
|
factory is SingleInstanceHandlerFactory && |
||||
|
(factory as SingleInstanceHandlerFactory).HandlerInstance == handler |
||||
|
); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
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()); |
||||
|
} |
||||
|
|
||||
|
public override async Task PublishAsync(Type eventType, object eventData) |
||||
|
{ |
||||
|
var exceptions = new List<Exception>(); |
||||
|
|
||||
|
await TriggerHandlersAsync(eventType, eventData, exceptions); |
||||
|
|
||||
|
if (exceptions.Any()) |
||||
|
{ |
||||
|
if (exceptions.Count == 1) |
||||
|
{ |
||||
|
exceptions[0].ReThrow(); |
||||
|
} |
||||
|
|
||||
|
throw new AggregateException("More than one error has occurred while triggering the event: " + eventType, exceptions); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
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(); |
||||
|
} |
||||
|
|
||||
|
private List<IEventHandlerFactory> GetOrCreateHandlerFactories(Type eventType) |
||||
|
{ |
||||
|
return HandlerFactories.GetOrAdd(eventType, (type) => 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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,12 +1,12 @@ |
|||||
using Volo.Abp.Collections; |
using Volo.Abp.Collections; |
||||
|
|
||||
namespace Volo.Abp.EventBus |
namespace Volo.Abp.EventBus.Local |
||||
{ |
{ |
||||
public class EventBusOptions |
public class LocalEventBusOptions |
||||
{ |
{ |
||||
public ITypeList<IEventHandler> Handlers { get; } |
public ITypeList<IEventHandler> Handlers { get; } |
||||
|
|
||||
public EventBusOptions() |
public LocalEventBusOptions() |
||||
{ |
{ |
||||
Handlers = new TypeList<IEventHandler>(); |
Handlers = new TypeList<IEventHandler>(); |
||||
} |
} |
||||
@ -1,90 +0,0 @@ |
|||||
using System; |
|
||||
using System.Threading.Tasks; |
|
||||
|
|
||||
namespace Volo.Abp.EventBus |
|
||||
{ |
|
||||
public sealed class NullEventBus : IEventBus |
|
||||
{ |
|
||||
public static NullEventBus Instance { get; } = new NullEventBus(); |
|
||||
|
|
||||
private NullEventBus() |
|
||||
{ |
|
||||
|
|
||||
} |
|
||||
|
|
||||
public IDisposable Register<TEvent>(Func<TEvent, Task> action) where TEvent : class |
|
||||
{ |
|
||||
return NullDisposable.Instance; |
|
||||
} |
|
||||
|
|
||||
public IDisposable Register<TEvent>(IEventHandler<TEvent> handler) where TEvent : class |
|
||||
{ |
|
||||
return NullDisposable.Instance; |
|
||||
} |
|
||||
|
|
||||
public IDisposable Register<TEvent, THandler>() where TEvent : class where THandler : IEventHandler, new() |
|
||||
{ |
|
||||
return NullDisposable.Instance; |
|
||||
} |
|
||||
|
|
||||
public IDisposable Register(Type eventType, IEventHandler handler) |
|
||||
{ |
|
||||
return NullDisposable.Instance; |
|
||||
} |
|
||||
|
|
||||
public IDisposable Register<TEvent>(IEventHandlerFactory factory) where TEvent : class |
|
||||
{ |
|
||||
return NullDisposable.Instance; |
|
||||
} |
|
||||
|
|
||||
public IDisposable Register(Type eventType, IEventHandlerFactory factory) |
|
||||
{ |
|
||||
return NullDisposable.Instance; |
|
||||
} |
|
||||
|
|
||||
public void Unregister<TEvent>(Func<TEvent, Task> action) where TEvent : class |
|
||||
{ |
|
||||
|
|
||||
} |
|
||||
|
|
||||
public void Unregister<TEvent>(IEventHandler<TEvent> handler) where TEvent : class |
|
||||
{ |
|
||||
|
|
||||
} |
|
||||
|
|
||||
public void Unregister(Type eventType, IEventHandler handler) |
|
||||
{ |
|
||||
|
|
||||
} |
|
||||
|
|
||||
public void Unregister<TEvent>(IEventHandlerFactory factory) where TEvent : class |
|
||||
{ |
|
||||
|
|
||||
} |
|
||||
|
|
||||
public void Unregister(Type eventType, IEventHandlerFactory factory) |
|
||||
{ |
|
||||
|
|
||||
} |
|
||||
|
|
||||
public void UnregisterAll<TEvent>() where TEvent : class |
|
||||
{ |
|
||||
|
|
||||
} |
|
||||
|
|
||||
public void UnregisterAll(Type eventType) |
|
||||
{ |
|
||||
|
|
||||
} |
|
||||
|
|
||||
public Task TriggerAsync<TEvent>(TEvent eventData) where TEvent : class |
|
||||
{ |
|
||||
return Task.CompletedTask; |
|
||||
} |
|
||||
|
|
||||
public Task TriggerAsync(Type eventType, object eventData) |
|
||||
{ |
|
||||
return Task.CompletedTask; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,91 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.EventBus.Local; |
||||
|
|
||||
|
namespace Volo.Abp.EventBus |
||||
|
{ |
||||
|
public sealed class NullLocalEventBus : ILocalEventBus |
||||
|
{ |
||||
|
public static NullLocalEventBus Instance { get; } = new NullLocalEventBus(); |
||||
|
|
||||
|
private NullLocalEventBus() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public IDisposable Subscribe<TEvent>(Func<TEvent, Task> action) where TEvent : class |
||||
|
{ |
||||
|
return NullDisposable.Instance; |
||||
|
} |
||||
|
|
||||
|
public IDisposable Subscribe<TEvent>(IEventHandler<TEvent> handler) where TEvent : class |
||||
|
{ |
||||
|
return NullDisposable.Instance; |
||||
|
} |
||||
|
|
||||
|
public IDisposable Subscribe<TEvent, THandler>() where TEvent : class where THandler : IEventHandler, new() |
||||
|
{ |
||||
|
return NullDisposable.Instance; |
||||
|
} |
||||
|
|
||||
|
public IDisposable Subscribe(Type eventType, IEventHandler handler) |
||||
|
{ |
||||
|
return NullDisposable.Instance; |
||||
|
} |
||||
|
|
||||
|
public IDisposable Subscribe<TEvent>(IEventHandlerFactory factory) where TEvent : class |
||||
|
{ |
||||
|
return NullDisposable.Instance; |
||||
|
} |
||||
|
|
||||
|
public IDisposable Subscribe(Type eventType, IEventHandlerFactory factory) |
||||
|
{ |
||||
|
return NullDisposable.Instance; |
||||
|
} |
||||
|
|
||||
|
public void Unsubscribe<TEvent>(Func<TEvent, Task> action) where TEvent : class |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public void Unsubscribe<TEvent>(IEventHandler<TEvent> handler) where TEvent : class |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public void Unsubscribe(Type eventType, IEventHandler handler) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public void Unsubscribe<TEvent>(IEventHandlerFactory factory) where TEvent : class |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public void Unsubscribe(Type eventType, IEventHandlerFactory factory) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public void UnsubscribeAll<TEvent>() where TEvent : class |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public void UnsubscribeAll(Type eventType) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public Task PublishAsync<TEvent>(TEvent eventData) where TEvent : class |
||||
|
{ |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
public Task PublishAsync(Type eventType, object eventData) |
||||
|
{ |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
namespace Volo.Abp.EventBus.Distributed |
||||
|
{ |
||||
|
public abstract class LocalDistributedEventBusTestBase : AbpIntegratedTest<EventBusTestModule> |
||||
|
{ |
||||
|
protected LocalDistributedEventBus LocalEventBus; |
||||
|
|
||||
|
protected LocalDistributedEventBusTestBase() |
||||
|
{ |
||||
|
LocalEventBus = GetRequiredService<LocalDistributedEventBus>(); |
||||
|
} |
||||
|
|
||||
|
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
||||
|
{ |
||||
|
options.UseAutofac(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.EventBus.Distributed |
||||
|
{ |
||||
|
public class LocalDistributedEventBus_Test : LocalDistributedEventBusTestBase |
||||
|
{ |
||||
|
[Fact] |
||||
|
public async Task Should_Call_Handler_AndDispose() |
||||
|
{ |
||||
|
LocalEventBus.Subscribe<MySimpleEventData, MySimpleDistributedTransientEventHandler>(); |
||||
|
|
||||
|
await LocalEventBus.PublishAsync(new MySimpleEventData(1)); |
||||
|
await LocalEventBus.PublishAsync(new MySimpleEventData(2)); |
||||
|
await LocalEventBus.PublishAsync(new MySimpleEventData(3)); |
||||
|
|
||||
|
Assert.Equal(3, MySimpleDistributedTransientEventHandler.HandleCount); |
||||
|
Assert.Equal(3, MySimpleDistributedTransientEventHandler.DisposeCount); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Volo.Abp.EventBus.Distributed |
||||
|
{ |
||||
|
public class MySimpleDistributedTransientEventHandler : IDistributedEventHandler<MySimpleEventData>, IDisposable |
||||
|
{ |
||||
|
public static int HandleCount { get; set; } |
||||
|
|
||||
|
public static int DisposeCount { get; set; } |
||||
|
|
||||
|
public Task HandleEventAsync(MySimpleEventData eventData) |
||||
|
{ |
||||
|
++HandleCount; |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
++DisposeCount; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,48 +0,0 @@ |
|||||
using System.Threading.Tasks; |
|
||||
using Xunit; |
|
||||
|
|
||||
namespace Volo.Abp.EventBus |
|
||||
{ |
|
||||
public class InheritanceTest : EventBusTestBase |
|
||||
{ |
|
||||
[Fact] |
|
||||
public async Task Should_Handle_Events_For_Derived_Classes() |
|
||||
{ |
|
||||
var totalData = 0; |
|
||||
|
|
||||
EventBus.Register<MySimpleEventData>( |
|
||||
eventData => |
|
||||
{ |
|
||||
totalData += eventData.Value; |
|
||||
return Task.CompletedTask; |
|
||||
}); |
|
||||
|
|
||||
await EventBus.TriggerAsync(new MySimpleEventData(1)); //Should handle directly registered class
|
|
||||
await EventBus.TriggerAsync(new MySimpleEventData(2)); //Should handle directly registered class
|
|
||||
await EventBus.TriggerAsync(new MyDerivedEventData(3)); //Should handle derived class too
|
|
||||
await EventBus.TriggerAsync(new MyDerivedEventData(4)); //Should handle derived class too
|
|
||||
|
|
||||
Assert.Equal(10, totalData); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public async Task Should_Not_Handle_Events_For_Base_Classes() |
|
||||
{ |
|
||||
var totalData = 0; |
|
||||
|
|
||||
EventBus.Register<MyDerivedEventData>( |
|
||||
eventData => |
|
||||
{ |
|
||||
totalData += eventData.Value; |
|
||||
return Task.CompletedTask; |
|
||||
}); |
|
||||
|
|
||||
await EventBus.TriggerAsync(new MySimpleEventData(1)); //Should not handle
|
|
||||
await EventBus.TriggerAsync(new MySimpleEventData(2)); //Should not handle
|
|
||||
await EventBus.TriggerAsync(new MyDerivedEventData(3)); //Should handle
|
|
||||
await EventBus.TriggerAsync(new MyDerivedEventData(4)); //Should handle
|
|
||||
|
|
||||
Assert.Equal(7, totalData); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,12 +1,12 @@ |
|||||
namespace Volo.Abp.EventBus |
namespace Volo.Abp.EventBus.Local |
||||
{ |
{ |
||||
public abstract class EventBusTestBase : AbpIntegratedTest<EventBusTestModule> |
public abstract class EventBusTestBase : AbpIntegratedTest<EventBusTestModule> |
||||
{ |
{ |
||||
protected IEventBus EventBus; |
protected ILocalEventBus LocalEventBus; |
||||
|
|
||||
protected EventBusTestBase() |
protected EventBusTestBase() |
||||
{ |
{ |
||||
EventBus = GetRequiredService<IEventBus>(); |
LocalEventBus = GetRequiredService<ILocalEventBus>(); |
||||
} |
} |
||||
|
|
||||
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) |
||||
@ -0,0 +1,48 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.EventBus.Local |
||||
|
{ |
||||
|
public class InheritanceTest : EventBusTestBase |
||||
|
{ |
||||
|
[Fact] |
||||
|
public async Task Should_Handle_Events_For_Derived_Classes() |
||||
|
{ |
||||
|
var totalData = 0; |
||||
|
|
||||
|
LocalEventBus.Subscribe<MySimpleEventData>( |
||||
|
eventData => |
||||
|
{ |
||||
|
totalData += eventData.Value; |
||||
|
return Task.CompletedTask; |
||||
|
}); |
||||
|
|
||||
|
await LocalEventBus.PublishAsync(new MySimpleEventData(1)); //Should handle directly registered class
|
||||
|
await LocalEventBus.PublishAsync(new MySimpleEventData(2)); //Should handle directly registered class
|
||||
|
await LocalEventBus.PublishAsync(new MyDerivedEventData(3)); //Should handle derived class too
|
||||
|
await LocalEventBus.PublishAsync(new MyDerivedEventData(4)); //Should handle derived class too
|
||||
|
|
||||
|
Assert.Equal(10, totalData); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_Not_Handle_Events_For_Base_Classes() |
||||
|
{ |
||||
|
var totalData = 0; |
||||
|
|
||||
|
LocalEventBus.Subscribe<MyDerivedEventData>( |
||||
|
eventData => |
||||
|
{ |
||||
|
totalData += eventData.Value; |
||||
|
return Task.CompletedTask; |
||||
|
}); |
||||
|
|
||||
|
await LocalEventBus.PublishAsync(new MySimpleEventData(1)); //Should not handle
|
||||
|
await LocalEventBus.PublishAsync(new MySimpleEventData(2)); //Should not handle
|
||||
|
await LocalEventBus.PublishAsync(new MyDerivedEventData(3)); //Should handle
|
||||
|
await LocalEventBus.PublishAsync(new MyDerivedEventData(4)); //Should handle
|
||||
|
|
||||
|
Assert.Equal(7, totalData); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,7 +1,7 @@ |
|||||
using System.Threading.Tasks; |
using System.Threading.Tasks; |
||||
using Volo.Abp.DependencyInjection; |
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
namespace Volo.Abp.EventBus |
namespace Volo.Abp.EventBus.Local |
||||
{ |
{ |
||||
public class MySimpleEventDataHandler : IEventHandler<MySimpleEventData>, ISingletonDependency |
public class MySimpleEventDataHandler : IEventHandler<MySimpleEventData>, ISingletonDependency |
||||
{ |
{ |
||||
@ -1,7 +1,7 @@ |
|||||
using System; |
using System; |
||||
using System.Threading.Tasks; |
using System.Threading.Tasks; |
||||
|
|
||||
namespace Volo.Abp.EventBus |
namespace Volo.Abp.EventBus.Local |
||||
{ |
{ |
||||
public class MySimpleTransientEventHandler : IEventHandler<MySimpleEventData>, IDisposable |
public class MySimpleTransientEventHandler : IEventHandler<MySimpleEventData>, IDisposable |
||||
{ |
{ |
||||
@ -1,18 +1,18 @@ |
|||||
using System.Threading.Tasks; |
using System.Threading.Tasks; |
||||
using Xunit; |
using Xunit; |
||||
|
|
||||
namespace Volo.Abp.EventBus |
namespace Volo.Abp.EventBus.Local |
||||
{ |
{ |
||||
public class TransientDisposableEventHandlerTest : EventBusTestBase |
public class TransientDisposableEventHandlerTest : EventBusTestBase |
||||
{ |
{ |
||||
[Fact] |
[Fact] |
||||
public async Task Should_Call_Handler_AndDispose() |
public async Task Should_Call_Handler_AndDispose() |
||||
{ |
{ |
||||
EventBus.Register<MySimpleEventData, MySimpleTransientEventHandler>(); |
LocalEventBus.Subscribe<MySimpleEventData, MySimpleTransientEventHandler>(); |
||||
|
|
||||
await EventBus.TriggerAsync(new MySimpleEventData(1)); |
await LocalEventBus.PublishAsync(new MySimpleEventData(1)); |
||||
await EventBus.TriggerAsync(new MySimpleEventData(2)); |
await LocalEventBus.PublishAsync(new MySimpleEventData(2)); |
||||
await EventBus.TriggerAsync(new MySimpleEventData(3)); |
await LocalEventBus.PublishAsync(new MySimpleEventData(3)); |
||||
|
|
||||
Assert.Equal(3, MySimpleTransientEventHandler.HandleCount); |
Assert.Equal(3, MySimpleTransientEventHandler.HandleCount); |
||||
Assert.Equal(3, MySimpleTransientEventHandler.DisposeCount); |
Assert.Equal(3, MySimpleTransientEventHandler.DisposeCount); |
||||
Loading…
Reference in new issue