forked from mikoskinen/Blazor.EventAggregator
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Removed outdated project properties Added appveyor.yml Added .editorconfig
- Loading branch information
Showing
10 changed files
with
211 additions
and
249 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,186 +1,149 @@ | ||
using Microsoft.AspNetCore.Components; | ||
using System; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Reflection; | ||
using System.Threading.Tasks; | ||
using JetBrains.Annotations; | ||
using Microsoft.AspNetCore.Components; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Options; | ||
|
||
namespace EventAggregator.Blazor | ||
namespace Nefarius.Blazor.EventAggregator; | ||
|
||
[UsedImplicitly] | ||
public class EventAggregator : IEventAggregator | ||
{ | ||
public class EventAggregator : IEventAggregator | ||
{ | ||
private readonly EventAggregatorOptions _options; | ||
private readonly List<Handler> _handlers = new List<Handler>(); | ||
|
||
public EventAggregator(IOptions<EventAggregatorOptions> options) | ||
{ | ||
_options = options.Value; | ||
} | ||
|
||
/// <inheritdoc /> | ||
public virtual void Subscribe(object subscriber) | ||
{ | ||
if (subscriber == null) | ||
{ | ||
throw new ArgumentNullException(nameof(subscriber)); | ||
} | ||
|
||
lock (_handlers) | ||
{ | ||
if (_handlers.Any(x => x.Matches(subscriber))) | ||
{ | ||
return; | ||
} | ||
|
||
_handlers.Add(new Handler(subscriber)); | ||
} | ||
} | ||
|
||
/// <inheritdoc /> | ||
public virtual void Unsubscribe(object subscriber) | ||
{ | ||
if (subscriber == null) | ||
{ | ||
throw new ArgumentNullException(nameof(subscriber)); | ||
} | ||
|
||
lock (_handlers) | ||
{ | ||
var handlersFound = _handlers.FirstOrDefault(x => x.Matches(subscriber)); | ||
|
||
if (handlersFound != null) | ||
{ | ||
_handlers.Remove(handlersFound); | ||
} | ||
} | ||
} | ||
|
||
public virtual async Task PublishAsync(object message) | ||
{ | ||
if (message == null) | ||
{ | ||
throw new ArgumentNullException(nameof(message)); | ||
} | ||
|
||
Handler[] handlersToNotify; | ||
|
||
lock (_handlers) | ||
{ | ||
handlersToNotify = _handlers.ToArray(); | ||
} | ||
|
||
var messageType = message.GetType(); | ||
|
||
var tasks = handlersToNotify.Select(h => h.Handle(messageType, message)); | ||
|
||
await Task.WhenAll(tasks); | ||
|
||
if (_options.AutoRefresh) | ||
{ | ||
foreach (var handler in handlersToNotify.Where(x => !x.IsDead)) | ||
{ | ||
if (!(handler.Reference.Target is ComponentBase component)) | ||
{ | ||
continue; | ||
} | ||
|
||
var invoker = component.GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic) | ||
.FirstOrDefault(x => | ||
string.Equals(x.Name, "InvokeAsync") && | ||
x.GetParameters().FirstOrDefault()?.ParameterType == typeof(Action)); | ||
|
||
if (invoker == null) | ||
{ | ||
continue; | ||
} | ||
|
||
var stateHasChangedMethod = component.GetType() | ||
.GetMethod("StateHasChanged", BindingFlags.Instance | BindingFlags.NonPublic); | ||
|
||
if (stateHasChangedMethod == null) | ||
{ | ||
continue; | ||
} | ||
|
||
var args = new object[] { new Action(() => stateHasChangedMethod.Invoke(component, null)) }; | ||
var tOut = (Task) invoker.Invoke(component, args); | ||
|
||
await tOut; | ||
} | ||
} | ||
|
||
var deadHandlers = handlersToNotify.Where(h => h.IsDead).ToList(); | ||
|
||
if (deadHandlers.Any()) | ||
{ | ||
lock (_handlers) | ||
{ | ||
foreach (var item in deadHandlers) | ||
{ | ||
_handlers.Remove(item); | ||
} | ||
} | ||
} | ||
} | ||
|
||
private class Handler | ||
{ | ||
private readonly WeakReference _reference; | ||
private readonly Dictionary<Type, MethodInfo> _supportedHandlers = new Dictionary<Type, MethodInfo>(); | ||
|
||
public Handler(object handler) | ||
{ | ||
_reference = new WeakReference(handler); | ||
|
||
var interfaces = handler.GetType().GetTypeInfo().ImplementedInterfaces | ||
.Where(x => x.GetTypeInfo().IsGenericType && x.GetGenericTypeDefinition() == typeof(IHandle<>)); | ||
|
||
foreach (var handleInterface in interfaces) | ||
{ | ||
var type = handleInterface.GetTypeInfo().GenericTypeArguments[0]; | ||
var method = handleInterface.GetRuntimeMethod("HandleAsync", new[] {type}); | ||
|
||
if (method != null) | ||
{ | ||
_supportedHandlers[type] = method; | ||
} | ||
} | ||
} | ||
|
||
public bool IsDead => _reference.Target == null; | ||
|
||
public WeakReference Reference => _reference; | ||
|
||
public bool Matches(object instance) | ||
{ | ||
return _reference.Target == instance; | ||
} | ||
|
||
public Task Handle(Type messageType, object message) | ||
{ | ||
var target = _reference.Target; | ||
|
||
if (target == null) | ||
{ | ||
return Task.FromResult(false); | ||
} | ||
|
||
var tasks = _supportedHandlers | ||
.Where(handler => handler.Key.GetTypeInfo().IsAssignableFrom(messageType.GetTypeInfo())) | ||
.Select(pair => pair.Value.Invoke(target, new[] {message})) | ||
.Select(result => (Task) result) | ||
.ToList(); | ||
|
||
return Task.WhenAll(tasks); | ||
} | ||
|
||
public bool Handles(Type messageType) | ||
{ | ||
return _supportedHandlers.Any( | ||
pair => pair.Key.GetTypeInfo().IsAssignableFrom(messageType.GetTypeInfo())); | ||
} | ||
} | ||
} | ||
private readonly List<Handler> _handlers = new(); | ||
private readonly EventAggregatorOptions _options; | ||
|
||
public EventAggregator(IOptions<EventAggregatorOptions> options) | ||
{ | ||
_options = options.Value; | ||
} | ||
|
||
/// <inheritdoc /> | ||
public virtual void Subscribe(object subscriber) | ||
{ | ||
if (subscriber == null) throw new ArgumentNullException(nameof(subscriber)); | ||
|
||
lock (_handlers) | ||
{ | ||
if (_handlers.Any(x => x.Matches(subscriber))) return; | ||
|
||
_handlers.Add(new Handler(subscriber)); | ||
} | ||
} | ||
|
||
/// <inheritdoc /> | ||
public virtual void Unsubscribe(object subscriber) | ||
{ | ||
if (subscriber == null) throw new ArgumentNullException(nameof(subscriber)); | ||
|
||
lock (_handlers) | ||
{ | ||
var handlersFound = _handlers.FirstOrDefault(x => x.Matches(subscriber)); | ||
|
||
if (handlersFound != null) _handlers.Remove(handlersFound); | ||
} | ||
} | ||
|
||
public virtual async Task PublishAsync(object message) | ||
{ | ||
if (message == null) throw new ArgumentNullException(nameof(message)); | ||
|
||
Handler[] handlersToNotify; | ||
|
||
lock (_handlers) | ||
{ | ||
handlersToNotify = _handlers.ToArray(); | ||
} | ||
|
||
var messageType = message.GetType(); | ||
|
||
var tasks = handlersToNotify.Select(h => h.Handle(messageType, message)); | ||
|
||
await Task.WhenAll(tasks); | ||
|
||
if (_options.AutoRefresh) | ||
foreach (var handler in handlersToNotify.Where(x => !x.IsDead)) | ||
{ | ||
if (!(handler.Reference.Target is ComponentBase component)) continue; | ||
|
||
var invoker = component.GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic) | ||
.FirstOrDefault(x => | ||
string.Equals(x.Name, "InvokeAsync") && | ||
x.GetParameters().FirstOrDefault()?.ParameterType == typeof(Action)); | ||
|
||
if (invoker == null) continue; | ||
|
||
var stateHasChangedMethod = component.GetType() | ||
.GetMethod("StateHasChanged", BindingFlags.Instance | BindingFlags.NonPublic); | ||
|
||
if (stateHasChangedMethod == null) continue; | ||
|
||
var args = new object[] { new Action(() => stateHasChangedMethod.Invoke(component, null)) }; | ||
var tOut = (Task)invoker.Invoke(component, args); | ||
|
||
await tOut; | ||
} | ||
|
||
var deadHandlers = handlersToNotify.Where(h => h.IsDead).ToList(); | ||
|
||
if (deadHandlers.Any()) | ||
lock (_handlers) | ||
{ | ||
foreach (var item in deadHandlers) _handlers.Remove(item); | ||
} | ||
} | ||
|
||
private class Handler | ||
{ | ||
private readonly Dictionary<Type, MethodInfo> _supportedHandlers = new(); | ||
|
||
public Handler(object handler) | ||
{ | ||
Reference = new WeakReference(handler); | ||
|
||
var interfaces = handler.GetType().GetTypeInfo().ImplementedInterfaces | ||
.Where(x => x.GetTypeInfo().IsGenericType && x.GetGenericTypeDefinition() == typeof(IHandle<>)); | ||
|
||
foreach (var handleInterface in interfaces) | ||
{ | ||
var type = handleInterface.GetTypeInfo().GenericTypeArguments[0]; | ||
var method = handleInterface.GetRuntimeMethod("HandleAsync", new[] { type }); | ||
|
||
if (method != null) _supportedHandlers[type] = method; | ||
} | ||
} | ||
|
||
public bool IsDead => Reference.Target == null; | ||
|
||
public WeakReference Reference { get; } | ||
|
||
public bool Matches(object instance) | ||
{ | ||
return Reference.Target == instance; | ||
} | ||
|
||
public Task Handle(Type messageType, object message) | ||
{ | ||
var target = Reference.Target; | ||
|
||
if (target == null) return Task.FromResult(false); | ||
|
||
var tasks = _supportedHandlers | ||
.Where(handler => handler.Key.GetTypeInfo().IsAssignableFrom(messageType.GetTypeInfo())) | ||
.Select(pair => pair.Value.Invoke(target, new[] { message })) | ||
.Select(result => (Task)result) | ||
.ToList(); | ||
|
||
return Task.WhenAll(tasks); | ||
} | ||
|
||
public bool Handles(Type messageType) | ||
{ | ||
return _supportedHandlers.Any( | ||
pair => pair.Key.GetTypeInfo().IsAssignableFrom(messageType.GetTypeInfo())); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,12 @@ | ||
namespace Microsoft.Extensions.DependencyInjection | ||
// ReSharper disable once CheckNamespace | ||
|
||
namespace Microsoft.Extensions.DependencyInjection; | ||
|
||
public class EventAggregatorOptions | ||
{ | ||
public class EventAggregatorOptions | ||
{ | ||
/// <summary> | ||
/// If true, Event Aggregator tries to run ComponentBase.StateHasChanged for the target component after | ||
/// it has handled the message. | ||
/// </summary> | ||
public bool AutoRefresh { get; set; } | ||
} | ||
/// <summary> | ||
/// If true, Event Aggregator tries to run ComponentBase.StateHasChanged for the target component after | ||
/// it has handled the message. | ||
/// </summary> | ||
public bool AutoRefresh { get; set; } | ||
} |
Oops, something went wrong.