Skip to content

Commit

Permalink
Updated namespaces
Browse files Browse the repository at this point in the history
Removed outdated project properties
Added appveyor.yml
Added .editorconfig
  • Loading branch information
nefarius committed Nov 19, 2022
1 parent ddcdddd commit 2b75355
Show file tree
Hide file tree
Showing 10 changed files with 211 additions and 249 deletions.
4 changes: 2 additions & 2 deletions src/Blazor.EventAggregator.sln → Blazor.EventAggregator.sln
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.4.33103.184
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nefarius.Blazor.EventAggregator", "Nefarius.Blazor.EventAggregator.csproj", "{C6623EF9-380D-44CF-A495-39461E78C3EF}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nefarius.Blazor.EventAggregator", "src\Nefarius.Blazor.EventAggregator.csproj", "{C6623EF9-380D-44CF-A495-39461E78C3EF}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{E87D2F2C-D4A3-4144-BA7A-000B87A2705D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodeBehind", "..\samples\CodeBehind\CodeBehind.csproj", "{AA15D944-29D1-4E8F-9AD1-FCBF087A27E2}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodeBehind", "samples\CodeBehind\CodeBehind.csproj", "{AA15D944-29D1-4E8F-9AD1-FCBF087A27E2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
4 changes: 2 additions & 2 deletions samples/CodeBehind/Pages/Counter.razor.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using EventAggregator.Blazor;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Nefarius.Blazor.EventAggregator;

namespace CodeBehind.Pages
{
Expand Down
4 changes: 2 additions & 2 deletions samples/CodeBehind/Pages/CounterListener.razor.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using EventAggregator.Blazor;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components;
using System.Threading.Tasks;
using Nefarius.Blazor.EventAggregator;

namespace CodeBehind.Pages
{
Expand Down
1 change: 0 additions & 1 deletion samples/CodeBehind/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using CodeBehind.Data;
using EventAggregator.Blazor;

namespace CodeBehind
{
Expand Down
317 changes: 140 additions & 177 deletions src/EventAggregator.cs
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()));
}
}
}
19 changes: 10 additions & 9 deletions src/EventAggregatorOptions.cs
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; }
}
Loading

0 comments on commit 2b75355

Please sign in to comment.