mirror of https://github.com/AMT-Cheif/Stylet.git
Add channels to the EventAggregator
This commit is contained in:
parent
ad4aa28b64
commit
6f63c5cc2d
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
@ -37,20 +38,23 @@ namespace Stylet
|
|||
/// Register an instance as wanting to receive events. Implement IHandle{T} for each event type you want to receive.
|
||||
/// </summary>
|
||||
/// <param name="handler">Instance that will be registered with the EventAggregator</param>
|
||||
void Subscribe(IHandle handler);
|
||||
/// <param name="channels">Channel(s) which should be subscribed to. Defaults to EventAggregator.DefaultChannel if none given</param>
|
||||
void Subscribe(IHandle handler, params string[] channels);
|
||||
|
||||
/// <summary>
|
||||
/// Unregister as wanting to receive events. The instance will no longer receive events after this is called.
|
||||
/// </summary>
|
||||
/// <param name="handler">Instance to unregister</param>
|
||||
void Unsubscribe(IHandle handler);
|
||||
/// <param name="channels">Channel(s) to unsubscribe from. Unsubscribes from everything if no channels given</param>
|
||||
void Unsubscribe(IHandle handler, params string[] channels);
|
||||
|
||||
/// <summary>
|
||||
/// Publish an event to all subscribers, using the specified dispatcher
|
||||
/// </summary>
|
||||
/// <param name="message">Event to publish</param>
|
||||
/// <param name="dispatcher">Dispatcher to use to call each subscriber's handle method(s)</param>
|
||||
void PublishWithDispatcher(object message, Action<Action> dispatcher);
|
||||
/// <param name="channels">Channel(s) to publish the message to. Defaults to EventAggregator.DefaultChannel none given</param>
|
||||
void PublishWithDispatcher(object message, Action<Action> dispatcher, params string[] channels);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -58,6 +62,11 @@ namespace Stylet
|
|||
/// </summary>
|
||||
public class EventAggregator : IEventAggregator
|
||||
{
|
||||
/// <summary>
|
||||
/// Channel which handlers are subscribed to / messages are published to, if no channels are named
|
||||
/// </summary>
|
||||
public static readonly string DefaultChannel = "DefaultChannel";
|
||||
|
||||
private readonly List<Handler> handlers = new List<Handler>();
|
||||
private readonly object handlersLock = new object();
|
||||
|
||||
|
@ -65,15 +74,17 @@ namespace Stylet
|
|||
/// Register an instance as wanting to receive events. Implement IHandle{T} for each event type you want to receive.
|
||||
/// </summary>
|
||||
/// <param name="handler">Instance that will be registered with the EventAggregator</param>
|
||||
public void Subscribe(IHandle handler)
|
||||
/// <param name="channels">Channel(s) which should be subscribed to. Defaults to EventAggregator.DefaultChannel if none given</param>
|
||||
public void Subscribe(IHandle handler, params string[] channels)
|
||||
{
|
||||
lock (this.handlersLock)
|
||||
{
|
||||
// Is it already subscribed?
|
||||
if (this.handlers.Any(x => x.IsHandlerForInstance(handler)))
|
||||
return;
|
||||
|
||||
this.handlers.Add(new Handler(handler));
|
||||
var subscribed = this.handlers.FirstOrDefault(x => x.IsHandlerForInstance(handler));
|
||||
if (subscribed == null)
|
||||
this.handlers.Add(new Handler(handler, channels)); // Adds default topic if appropriate
|
||||
else
|
||||
subscribed.SubscribeToChannels(channels);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,13 +92,18 @@ namespace Stylet
|
|||
/// Unregister as wanting to receive events. The instance will no longer receive events after this is called.
|
||||
/// </summary>
|
||||
/// <param name="handler">Instance to unregister</param>
|
||||
public void Unsubscribe(IHandle handler)
|
||||
/// <param name="channels">Channel(s) to unsubscribe from. Unsubscribes from everything if no channels given</param>
|
||||
public void Unsubscribe(IHandle handler, params string[] channels)
|
||||
{
|
||||
lock (this.handlersLock)
|
||||
{
|
||||
var existingHandler = this.handlers.FirstOrDefault(x => x.IsHandlerForInstance(handler));
|
||||
if (existingHandler != null)
|
||||
this.handlers.Remove(existingHandler);
|
||||
{
|
||||
if (existingHandler.UnsubscribeFromChannels(channels)) // Handles default topic appropriately
|
||||
this.handlers.Remove(existingHandler);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,12 +112,13 @@ namespace Stylet
|
|||
/// </summary>
|
||||
/// <param name="message">Event to publish</param>
|
||||
/// <param name="dispatcher">Dispatcher to use to call each subscriber's handle method(s)</param>
|
||||
public void PublishWithDispatcher(object message, Action<Action> dispatcher)
|
||||
/// <param name="channels">Channel(s) to publish the message to. Defaults to EventAggregator.DefaultChannel none given</param>
|
||||
public void PublishWithDispatcher(object message, Action<Action> dispatcher, params string[] channels)
|
||||
{
|
||||
lock (this.handlersLock)
|
||||
{
|
||||
var messageType = message.GetType();
|
||||
var deadHandlers = this.handlers.Where(x => !x.Handle(messageType, message, dispatcher)).ToArray();
|
||||
var deadHandlers = this.handlers.Where(x => !x.Handle(messageType, message, dispatcher, channels)).ToArray();
|
||||
foreach (var deadHandler in deadHandlers)
|
||||
{
|
||||
this.handlers.Remove(deadHandler);
|
||||
|
@ -113,8 +130,9 @@ namespace Stylet
|
|||
{
|
||||
private readonly WeakReference target;
|
||||
private readonly List<HandlerInvoker> invokers = new List<HandlerInvoker>();
|
||||
private HashSet<string> channels = new HashSet<string>();
|
||||
|
||||
public Handler(object handler)
|
||||
public Handler(object handler, string[] channels)
|
||||
{
|
||||
var handlerType = handler.GetType();
|
||||
this.target = new WeakReference(handler);
|
||||
|
@ -124,6 +142,10 @@ namespace Stylet
|
|||
var messageType = implementation.GetGenericArguments()[0];
|
||||
this.invokers.Add(new HandlerInvoker(handlerType, messageType, implementation.GetMethod("Handle")));
|
||||
}
|
||||
|
||||
if (channels.Length == 0)
|
||||
channels = new[] { EventAggregator.DefaultChannel };
|
||||
this.SubscribeToChannels(channels);
|
||||
}
|
||||
|
||||
public bool IsHandlerForInstance(object subscriber)
|
||||
|
@ -131,12 +153,33 @@ namespace Stylet
|
|||
return this.target.Target == subscriber;
|
||||
}
|
||||
|
||||
public bool Handle(Type messageType, object message, Action<Action> dispatcher)
|
||||
public void SubscribeToChannels(string[] channels)
|
||||
{
|
||||
this.channels.UnionWith(channels);
|
||||
}
|
||||
|
||||
public bool UnsubscribeFromChannels(string[] channels)
|
||||
{
|
||||
// If channels is empty, unsubscribe from everything
|
||||
if (channels.Length == 0)
|
||||
return true;
|
||||
this.channels.ExceptWith(channels);
|
||||
return this.channels.Count == 0;
|
||||
}
|
||||
|
||||
public bool Handle(Type messageType, object message, Action<Action> dispatcher, string[] channels)
|
||||
{
|
||||
var target = this.target.Target;
|
||||
if (target == null)
|
||||
return false;
|
||||
|
||||
if (channels.Length == 0)
|
||||
channels = new[] { EventAggregator.DefaultChannel };
|
||||
|
||||
// We're not subscribed to any of the channels
|
||||
if (!channels.All(x => this.channels.Contains(x)))
|
||||
return true;
|
||||
|
||||
foreach (var invoker in this.invokers)
|
||||
{
|
||||
invoker.Invoke(target, messageType, message, dispatcher);
|
||||
|
@ -180,9 +223,10 @@ namespace Stylet
|
|||
/// </summary>
|
||||
/// <param name="eventAggregator">EventAggregator to publish the message with</param>
|
||||
/// <param name="message">Event to publish</param>
|
||||
public static void PublishOnUIThread(this IEventAggregator eventAggregator, object message)
|
||||
/// <param name="channels">Channel(s) to publish the message to. Defaults to EventAggregator.DefaultChannel none given</param>
|
||||
public static void PublishOnUIThread(this IEventAggregator eventAggregator, object message, params string[] channels)
|
||||
{
|
||||
eventAggregator.PublishWithDispatcher(message, Execute.OnUIThread);
|
||||
eventAggregator.PublishWithDispatcher(message, Execute.OnUIThread, channels);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -190,9 +234,10 @@ namespace Stylet
|
|||
/// </summary>
|
||||
/// <param name="eventAggregator">EventAggregator to publish the message with</param>
|
||||
/// <param name="message">Event to publish</param>
|
||||
public static void Publish(this IEventAggregator eventAggregator, object message)
|
||||
/// <param name="channels">Channel(s) to publish the message to. Defaults to EventAggregator.DefaultChannel none given</param>
|
||||
public static void Publish(this IEventAggregator eventAggregator, object message, params string[] channels)
|
||||
{
|
||||
eventAggregator.PublishWithDispatcher(message, a => a());
|
||||
eventAggregator.PublishWithDispatcher(message, a => a(), channels);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,21 +34,28 @@ namespace StyletUnitTests
|
|||
public void Handle(M1 message) { throw new Exception("Should not be called. Ever"); }
|
||||
}
|
||||
|
||||
private EventAggregator ea;
|
||||
|
||||
[TestFixtureSetUp]
|
||||
public void SetUpFixture()
|
||||
{
|
||||
Execute.TestExecuteSynchronously = true;
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
this.ea = new EventAggregator();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SubscribesAndDeliversExactMessage()
|
||||
{
|
||||
var ea = new EventAggregator();
|
||||
var target = new C1();
|
||||
ea.Subscribe(target);
|
||||
this.ea.Subscribe(target);
|
||||
|
||||
var message = new M1();
|
||||
ea.Publish(message);
|
||||
this.ea.Publish(message);
|
||||
|
||||
Assert.AreEqual(message, target.ReceivedMessage);
|
||||
}
|
||||
|
@ -56,12 +63,11 @@ namespace StyletUnitTests
|
|||
[Test]
|
||||
public void DeliversToAllHandlersIncludingDerived()
|
||||
{
|
||||
var ea = new EventAggregator();
|
||||
var target = new C2();
|
||||
ea.Subscribe(target);
|
||||
this.ea.Subscribe(target);
|
||||
|
||||
var message = new M2();
|
||||
ea.Publish(message);
|
||||
this.ea.Publish(message);
|
||||
|
||||
Assert.AreEqual(message, target.ReceivedM1);
|
||||
Assert.AreEqual(message, target.ReceivedM2);
|
||||
|
@ -70,13 +76,12 @@ namespace StyletUnitTests
|
|||
[Test]
|
||||
public void UnsubscribeUnsubscribes()
|
||||
{
|
||||
var ea = new EventAggregator();
|
||||
var target = new C1();
|
||||
ea.Subscribe(target);
|
||||
ea.Unsubscribe(target);
|
||||
this.ea.Subscribe(target);
|
||||
this.ea.Unsubscribe(target);
|
||||
|
||||
var message = new M1();
|
||||
ea.Publish(message);
|
||||
this.ea.Publish(message);
|
||||
|
||||
Assert.IsNull(target.ReceivedMessage);
|
||||
}
|
||||
|
@ -84,29 +89,27 @@ namespace StyletUnitTests
|
|||
[Test]
|
||||
public void TargetReferenceIsWeak()
|
||||
{
|
||||
var ea = new EventAggregator();
|
||||
var target = new C3();
|
||||
var weaktarget = new WeakReference(target);
|
||||
ea.Subscribe(target);
|
||||
this.ea.Subscribe(target);
|
||||
|
||||
// Ugly, but it's the only way to test a WeakReference...
|
||||
target = null;
|
||||
GC.Collect();
|
||||
|
||||
Assert.DoesNotThrow(() => ea.Publish(new M1()));
|
||||
Assert.DoesNotThrow(() => this.ea.Publish(new M1()));
|
||||
Assert.IsNull(weaktarget.Target);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SubscribingTwiceDoesNothing()
|
||||
{
|
||||
var ea = new EventAggregator();
|
||||
var target = new C1();
|
||||
ea.Subscribe(target);
|
||||
ea.Subscribe(target);
|
||||
this.ea.Subscribe(target);
|
||||
this.ea.Subscribe(target);
|
||||
|
||||
var message = new M1();
|
||||
ea.Publish(message);
|
||||
this.ea.Publish(message);
|
||||
|
||||
Assert.AreEqual(1, target.ReceivedMessageCount);
|
||||
}
|
||||
|
@ -114,14 +117,128 @@ namespace StyletUnitTests
|
|||
[Test]
|
||||
public void PublishOnUIThreadPublishedOnUIThread()
|
||||
{
|
||||
var ea = new EventAggregator();
|
||||
var target = new C1();
|
||||
ea.Subscribe(target);
|
||||
this.ea.Subscribe(target);
|
||||
|
||||
var message = new M1();
|
||||
ea.PublishOnUIThread(message);
|
||||
this.ea.PublishOnUIThread(message);
|
||||
|
||||
Assert.AreEqual(message, target.ReceivedMessage);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TargetReceivesMessagesOnTopicsItIsSubscribedTo()
|
||||
{
|
||||
var target = new C1();
|
||||
this.ea.Subscribe(target, "C1", "C2");
|
||||
|
||||
var message = new M1();
|
||||
this.ea.Publish(message, "C1");
|
||||
this.ea.Publish(message, "C2");
|
||||
|
||||
Assert.AreEqual(2, target.ReceivedMessageCount);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TargetDoesNotReceiveMessagesOnTopicsItIsNotSubscribedTo()
|
||||
{
|
||||
var target = new C1();
|
||||
this.ea.Subscribe(target, "C1");
|
||||
|
||||
var message = new M1();
|
||||
this.ea.Publish(message, "C2");
|
||||
|
||||
Assert.AreEqual(0, target.ReceivedMessageCount);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SubscribesToDefaultChannelByDefault()
|
||||
{
|
||||
var target = new C1();
|
||||
this.ea.Subscribe(target);
|
||||
|
||||
var message = new M1();
|
||||
this.ea.Publish(message, EventAggregator.DefaultChannel);
|
||||
|
||||
Assert.AreEqual(message, target.ReceivedMessage);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DoesNotSubscribeToDefaultChannelIfAChannelIsGiven()
|
||||
{
|
||||
var target = new C1();
|
||||
this.ea.Subscribe(target, "C1");
|
||||
|
||||
var message = new M1();
|
||||
this.ea.Publish(message);
|
||||
|
||||
Assert.AreEqual(0, target.ReceivedMessageCount);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DoesNotPublishToDefaultChannelIfAChannelIsGiven()
|
||||
{
|
||||
var target = new C1();
|
||||
this.ea.Subscribe(target);
|
||||
|
||||
var message = new M1();
|
||||
this.ea.Publish(message, "C1");
|
||||
|
||||
Assert.AreEqual(0, target.ReceivedMessageCount);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AdditionalChannelsCanBeSubscribedTo()
|
||||
{
|
||||
var target = new C1();
|
||||
this.ea.Subscribe(target, "C1");
|
||||
this.ea.Subscribe(target, "C2");
|
||||
|
||||
var message = new M1();
|
||||
this.ea.Publish(message, "C1");
|
||||
this.ea.Publish(message, "C2");
|
||||
|
||||
Assert.AreEqual(2, target.ReceivedMessageCount);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IndividualChannelsCanBeUnsubscribedFrom()
|
||||
{
|
||||
var target = new C1();
|
||||
this.ea.Subscribe(target, "C1", "C2");
|
||||
this.ea.Unsubscribe(target, "C1");
|
||||
|
||||
var message = new M1();
|
||||
this.ea.Publish(message, "C1");
|
||||
Assert.AreEqual(0, target.ReceivedMessageCount);
|
||||
|
||||
this.ea.Publish(message, "C2");
|
||||
Assert.AreEqual(1, target.ReceivedMessageCount);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void UnsubscribeUnsubscribesFromEverythingIfNoChannelsGiven()
|
||||
{
|
||||
var target = new C1();
|
||||
this.ea.Subscribe(target, "C1", "C2");
|
||||
this.ea.Unsubscribe(target);
|
||||
|
||||
var message = new M1();
|
||||
this.ea.Publish(message, "C1", "C2");
|
||||
|
||||
Assert.AreEqual(0, target.ReceivedMessageCount);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MessagePublishedToMultipleChannelsGetsDeliveredOnce()
|
||||
{
|
||||
var target = new C1();
|
||||
this.ea.Subscribe(target, "C1", "C2");
|
||||
|
||||
var message = new M1();
|
||||
this.ea.Publish(message, "C1", "C2");
|
||||
|
||||
Assert.AreEqual(1, target.ReceivedMessageCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue