├── .gitignore ├── .idea └── .idea.MemBus │ └── .idea │ ├── indexLayout.xml │ └── vcs.xml ├── MemBus.Tests.Performance ├── CompositeSubscriptionPerformanceTest.cs ├── Helper.cs ├── IScenario.cs ├── MemBus.Tests.Performance.csproj ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── Simple.cs └── app.config ├── MemBus.Tests ├── Help │ ├── AssertExtensions.cs │ ├── FakeBus.cs │ ├── Helpers.cs │ ├── IClassicIHandleStuffI.cs │ ├── ItfNonGenericForHandles.cs │ ├── MessageA.cs │ ├── Messages.cs │ ├── MockSubscription.cs │ ├── NamedSubscription.cs │ ├── PublishPipelineTester.cs │ ├── RxBasedFooObservable.cs │ ├── SampleClass.cs │ ├── SimpleResolver.cs │ ├── SomeCrazyHandler.cs │ ├── SomeHandler.cs │ ├── SubscriptionThatFakesHandles.cs │ └── TestShaper.cs ├── Integration │ ├── IntegrationScenarioInteractionWithSubscriber.cs │ ├── UnexpectedDisposalOfSubscriptions.cs │ ├── When_using_disposable_buses.cs │ ├── When_using_ioc_support.cs │ ├── When_using_the_bus.cs │ ├── When_using_the_bus_in_the_ui.cs │ └── When_using_the_bus_with_flexible_subscriptions.cs ├── MemBus.Tests.csproj ├── Properties │ └── AssemblyInfo.cs ├── Publishing │ ├── Awaitable_Publish.cs │ ├── Using_Publish_Pipeline.cs │ └── Using_publishing_methods.cs ├── Rx │ ├── Observable_As_Publish.cs │ ├── Observable_As_Timer.cs │ └── When_MemBus_is_used_as_Observable.cs ├── Subscribing │ ├── FSA_Consuming_Observables.cs │ ├── FSA_Reacting_To_Messages_And_Sending.cs │ ├── FSA_Using_Interface_Based_Subscribing.cs │ ├── FSA_Using_Predicate_Based_Subscribing.cs │ ├── FlexibleSubscribingIntegrationContext.cs │ ├── Using_Composite_Subscription.cs │ ├── Using_SubscriptionAdapter_Service.cs │ ├── Using_disposable_method_subscription.cs │ ├── Using_subscription_shapes.cs │ ├── When_Resolving_Subscriptions.cs │ └── When_subscribing.cs ├── When_using_standard_resolver.cs └── packages.config ├── MemBus.sln ├── MemBus ├── Bus.cs ├── CompositeResolver.cs ├── CompositeSubscription.cs ├── Configurators │ ├── AsyncConfiguration.cs │ ├── Conservative.cs │ ├── Fast.cs │ ├── IoCSupport.cs │ └── RichClientFrontend.cs ├── IBus.cs ├── ISubscription.cs ├── ISubscriptionResolver.cs ├── IoCBasedResolver.cs ├── IocAdapter.cs ├── MemBus.csproj ├── MessageInfo.cs ├── MessageObservable.cs ├── Messages │ ├── ExceptionOccurred.cs │ └── MessageStreamCompleted.cs ├── Properties │ └── AssemblyInfo.cs ├── Publishing │ ├── DeferredPublishPipelineMember.cs │ ├── FireAndForgetPublisher.cs │ ├── IPublishPipelineMember.cs │ ├── ObservableRelay.cs │ ├── ParallelBlockingPublisher.cs │ ├── ParallelNonBlockingPublisher.cs │ ├── PublishChain.cs │ ├── PublishChainCasing.cs │ ├── PublishToken.cs │ └── SequentialPublisher.cs ├── RxEnabledObservable.cs ├── Setup │ ├── AdHocConfigurator.cs │ ├── BusSetup.cs │ ├── IConfigurableBus.cs │ ├── IConfigurablePublishing.cs │ ├── IConfigurableSubscribing.cs │ └── ISetup.cs ├── Subscriber.cs ├── Subscribing │ ├── Adapter │ │ ├── ConstructSubscriptionExtension.cs │ │ ├── FlexibleSubscribeAdapter.cs │ │ ├── IMethodInfoScanner.cs │ │ ├── InterfaceBasedBuilder.cs │ │ ├── MessageEndpointsBuilder.cs │ │ └── MethodBasedBuilder.cs │ ├── DisposableSubscription.cs │ ├── FilteredSubscription.cs │ ├── IAcceptDisposeToken.cs │ ├── IDenyShaper.cs │ ├── IKnowsSubscribedInstance.cs │ ├── ISubscriptionShaper.cs │ ├── MethodInvocation.cs │ ├── PublishingMethodInvocation.cs │ ├── ShapeToDispose.cs │ ├── ShapeToFilter.cs │ ├── ShapeToPassthrough.cs │ ├── ShapeToUIDispatch.cs │ ├── SubscriptionCustomizer.cs │ ├── SubscriptionPipeline.cs │ ├── SubscriptionShaperAggregate.cs │ └── UiDispatchingSubscription.cs └── Support │ ├── AbstractServices.cs │ ├── DisposeContainer.cs │ ├── IRequireServices.cs │ ├── IServices.cs │ ├── MemBusException.cs │ ├── Publish.cs │ ├── ReflectionExtensions.cs │ ├── SubscriptionCandidatesExtensions.cs │ └── UsefulExtensions.cs └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | obj 2 | bin 3 | out 4 | NDependOut* 5 | _cache 6 | _ReSharper.* 7 | *.user 8 | *.resharper 9 | *.ReSharper 10 | *.suo 11 | *.cache 12 | *.~ 13 | *.pidb 14 | *.userprefs 15 | TestResults 16 | test-results 17 | MemBus_mm_cache.bin 18 | packages 19 | Nuget/MemBus/lib 20 | Nuget/MemBus/readme.md 21 | *.swp 22 | *.nupkg 23 | *.Dotsettings 24 | *.pfx 25 | *.DS_Store 26 | .vs 27 | AppPackages 28 | readme.html 29 | .vscode 30 | # Common IntelliJ Platform excludes 31 | 32 | # User specific 33 | **/.idea/**/workspace.xml 34 | **/.idea/**/tasks.xml 35 | **/.idea/shelf/* 36 | **/.idea/dictionaries 37 | 38 | # Sensitive or high-churn files 39 | **/.idea/**/dataSources/ 40 | **/.idea/**/dataSources.ids 41 | **/.idea/**/dataSources.xml 42 | **/.idea/**/dataSources.local.xml 43 | **/.idea/**/sqlDataSources.xml 44 | **/.idea/**/dynamic.xml 45 | 46 | # Rider 47 | 48 | # Rider auto-generates .iml files, and contentModel.xml 49 | **/.idea/**/*.iml 50 | **/.idea/**/contentModel.xml 51 | **/.idea/**/modules.xml -------------------------------------------------------------------------------- /.idea/.idea.MemBus/.idea/indexLayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/.idea.MemBus/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /MemBus.Tests.Performance/CompositeSubscriptionPerformanceTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading; 6 | using MemBus.Support; 7 | 8 | namespace MemBus.Tests.Performance 9 | { 10 | public class CompositeSubscriptionPerformanceTest : IScenario 11 | { 12 | private static readonly Random R = new Random(); 13 | private const int DisposalProbability = 7000; 14 | private const int NumberOfStartingSubscriptions = 1500; 15 | private const int NumberOfMessagesSent = 5000; 16 | 17 | private readonly Dictionary> _subBuilders = new Dictionary> 18 | { 19 | {1, () => new ASubOf()}, 20 | {2, () => new ASubOf()}, 21 | {3, () => new ASubOf()} 22 | }; 23 | private readonly Dictionary> _messageBuilders = new Dictionary> 24 | { 25 | {0, () => new MessageA()}, 26 | {1, () => new MessageB()}, 27 | {2, () => new MessageC()} 28 | }; 29 | 30 | private CompositeSubscription _cSub; 31 | private ISubscriptionResolver _cRes; 32 | private bool _alreadyRun; 33 | private static int _disposeCount; 34 | 35 | public void Reset() 36 | { 37 | _alreadyRun = true; 38 | _disposeCount = 0; 39 | } 40 | 41 | public void Run(IBus bus, TextWriter writer) 42 | { 43 | if(!_alreadyRun) 44 | writer.WriteLine("Disposalprob - 1 in {0}, Starting subs {1}, Messages sent: {2}", DisposalProbability, NumberOfStartingSubscriptions, NumberOfMessagesSent); 45 | SetupCompositeSubscription(writer); 46 | 47 | var threadCounter1 = 0; 48 | var threadCounter2 = 0; 49 | 50 | var t1 = new Timer(_ => 51 | { 52 | threadCounter1++; 53 | AddSubscription(); 54 | }, null, 0, 1); 55 | 56 | var t2 = new Timer(_ => 57 | { 58 | threadCounter2++; 59 | AddSubscription(); 60 | }, null, 0, 1); 61 | 62 | PumpMessages(); 63 | 64 | if (!_alreadyRun) 65 | ListSubscriptions(writer, _cSub); 66 | t1.Dispose(); 67 | t2.Dispose(); 68 | writer.WriteLine("ThreadCounter1: " + threadCounter1 + ", ThreadCounter2: " + threadCounter2 + ", DisposeCounter: " + _disposeCount); 69 | } 70 | 71 | 72 | private void SetupCompositeSubscription(TextWriter writer) 73 | { 74 | var subs = Enumerable.Repeat(1, NumberOfStartingSubscriptions).Select(_ => NextSubBuilderIndex).Select(i => _subBuilders[i]()).ToList(); 75 | 76 | if (!_alreadyRun) 77 | ListSubscriptions(writer, subs); 78 | 79 | _cSub = new CompositeSubscription(subs); 80 | _cRes = _cSub; 81 | } 82 | 83 | private void AddSubscription() 84 | { 85 | _cSub.Add(_subBuilders[NextSubBuilderIndex]()); 86 | } 87 | 88 | private void PumpMessages() 89 | { 90 | for (var i = 0; i < NumberOfMessagesSent; i++) 91 | { 92 | var msg = _messageBuilders[i%3](); 93 | var subs = _cRes.GetSubscriptionsFor(msg); 94 | foreach(var s in subs) 95 | s.Push(msg); 96 | } 97 | } 98 | 99 | private static void ListSubscriptions(TextWriter writer, IEnumerable subs) 100 | { 101 | var groupCount = subs.GroupBy(sub => sub.GetType()) 102 | .Select(grp => new {Type = grp.Key.GetGenericArguments()[0].Name, Count = grp.Count()}) 103 | .ToList(); 104 | foreach (var g in groupCount) 105 | writer.WriteLine("Got " + g.Count + " subscriptions for " + g.Type); 106 | } 107 | 108 | private static int NextSubBuilderIndex 109 | { 110 | get { return R.Next(1, 4); } 111 | } 112 | 113 | private static bool ShouldDispose() 114 | { 115 | return R.Next(DisposalProbability) == 1; 116 | } 117 | 118 | public class ASubOf : IDisposableSubscription, IDisposable 119 | { 120 | public void Push(object message) 121 | { 122 | if (ShouldDispose()) 123 | { 124 | _disposeCount++; 125 | Disposed.Raise(this); 126 | } 127 | } 128 | 129 | public bool Handles(Type messageType) 130 | { 131 | return typeof (T).IsAssignableFrom(messageType); 132 | } 133 | 134 | public IDisposable GetDisposer() 135 | { 136 | return this; 137 | } 138 | 139 | public bool IsDisposed { get; private set; } 140 | public event EventHandler Disposed; 141 | 142 | void IDisposable.Dispose() 143 | { 144 | IsDisposed = true; 145 | } 146 | } 147 | 148 | private class MessageA { } 149 | private class MessageB { } 150 | private class MessageC { } 151 | 152 | } 153 | } -------------------------------------------------------------------------------- /MemBus.Tests.Performance/Helper.cs: -------------------------------------------------------------------------------- 1 | namespace MemBus.Tests.Performance 2 | { 3 | public class Helper 4 | { 5 | 6 | } 7 | } -------------------------------------------------------------------------------- /MemBus.Tests.Performance/IScenario.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace MemBus.Tests.Performance 4 | { 5 | public interface IScenario 6 | { 7 | void Run(IBus bus, TextWriter writer); 8 | void Reset(); 9 | } 10 | } -------------------------------------------------------------------------------- /MemBus.Tests.Performance/MemBus.Tests.Performance.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | x86 6 | 8.0.30703 7 | 2.0 8 | {F3A4F168-8958-430A-94DD-7689EB12553F} 9 | Exe 10 | Properties 11 | MemBus.Tests.Performance 12 | MemBus.Tests.Performance 13 | v4.5 14 | 15 | 16 | 512 17 | ..\..\MemBus\ 18 | true 19 | 20 | 21 | x86 22 | true 23 | full 24 | false 25 | bin\Debug\ 26 | DEBUG;TRACE 27 | prompt 28 | 4 29 | false 30 | 31 | 32 | x86 33 | pdbonly 34 | true 35 | bin\Release\ 36 | TRACE 37 | prompt 38 | 4 39 | false 40 | 41 | 42 | AnyCPU 43 | bin\Debug\ 44 | false 45 | 46 | 47 | AnyCPU 48 | bin\Release\ 49 | false 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | {9657a125-569e-4f7e-9370-1bf581c7fa0c} 74 | MemBus.Pcl 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /MemBus.Tests.Performance/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading; 4 | using MemBus.Configurators; 5 | 6 | namespace MemBus.Tests.Performance 7 | { 8 | class Program 9 | { 10 | private static void Main() 11 | { 12 | var bus = BusSetup.StartWith().Construct(); 13 | var s = new CompositeSubscriptionPerformanceTest(); 14 | for (var i = 0; i < 10;i++) 15 | Run(s, bus); 16 | Console.ReadLine(); 17 | } 18 | 19 | private static void Run(IScenario s, IBus bus) 20 | { 21 | var sw = Stopwatch.StartNew(); 22 | s.Run(bus, Console.Out); 23 | Console.WriteLine("Done in " + sw.Elapsed.TotalSeconds + " seconds"); 24 | Console.WriteLine("--"); 25 | s.Reset(); 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /MemBus.Tests.Performance/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("MemBus.Tests.Performance")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft")] 12 | [assembly: AssemblyProduct("MemBus.Tests.Performance")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft 2010")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("3a4fb1a2-1374-43f5-aa7a-9fd779c7a00c")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /MemBus.Tests.Performance/Simple.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Threading; 6 | using MemBus.Support; 7 | 8 | namespace MemBus.Tests.Performance 9 | { 10 | public class Simple : IScenario 11 | { 12 | private int _aCount; 13 | private int _bCount; 14 | private int _cCount; 15 | 16 | private readonly DisposeContainer _c = new DisposeContainer(); 17 | 18 | public void Run(IBus bus, TextWriter w) 19 | { 20 | _c.Add(bus.Subscribe(OnMessageA)); 21 | _c.Add(bus.Subscribe(OnMessageB)); 22 | _c.Add(bus.Subscribe(OnMessageC)); 23 | 24 | var r = new Random(); 25 | var dict = new Dictionary> 26 | { 27 | {0, () => new MessageA()}, 28 | {1, () => new MessageB()}, 29 | {2, () => new MessageC()}, 30 | }; 31 | int count = 0; 32 | var sw = Stopwatch.StartNew(); 33 | while (count < 100000) 34 | { 35 | bus.Publish(dict[r.Next(0, 3)]()); 36 | count++; 37 | } 38 | 39 | w.WriteLine("Through {0}", sw.ElapsedMilliseconds); 40 | 41 | while (_aCount != MessageA.Count && _bCount != MessageB.Count && _cCount != MessageC.Count) 42 | { 43 | WriteInfo(w); 44 | Thread.Sleep(1000); 45 | } 46 | WriteInfo(w); 47 | } 48 | 49 | private void WriteInfo(TextWriter w) 50 | { 51 | w.WriteLine("From MsgA:{0}({1}), B:{2}({3}), C:{4}({5})", _aCount, MessageA.Count, _bCount, 52 | MessageB.Count, _cCount, MessageC.Count); 53 | } 54 | 55 | public void Reset() 56 | { 57 | _c.Dispose(); 58 | MessageA.Reset(); 59 | MessageB.Reset(); 60 | MessageC.Reset(); 61 | _aCount = 0; 62 | _bCount = 0; 63 | _cCount = 0; 64 | } 65 | 66 | private void OnMessageC(MessageC obj) 67 | { 68 | Interlocked.Increment(ref _cCount); 69 | } 70 | 71 | private void OnMessageB(MessageB obj) 72 | { 73 | //Thread.Sleep(1); 74 | Interlocked.Increment(ref _bCount); 75 | } 76 | 77 | private void OnMessageA(MessageA obj) 78 | { 79 | Interlocked.Increment(ref _aCount); 80 | } 81 | 82 | class MessageA 83 | { 84 | public static int Count; 85 | 86 | public static void Reset() 87 | { 88 | Count = 0; 89 | } 90 | 91 | public MessageA() 92 | { 93 | Interlocked.Increment(ref Count); 94 | } 95 | } 96 | 97 | class MessageB 98 | { 99 | public static int Count; 100 | 101 | public static void Reset() 102 | { 103 | Count = 0; 104 | } 105 | 106 | public MessageB() 107 | { 108 | Interlocked.Increment(ref Count); 109 | } 110 | } 111 | 112 | class MessageC 113 | { 114 | public static int Count; 115 | 116 | public static void Reset() 117 | { 118 | Count = 0; 119 | } 120 | 121 | public MessageC() 122 | { 123 | Interlocked.Increment(ref Count); 124 | } 125 | } 126 | } 127 | } -------------------------------------------------------------------------------- /MemBus.Tests.Performance/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /MemBus.Tests/Help/AssertExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using Xunit; 6 | 7 | namespace MemBus.Tests.Help 8 | { 9 | public static class AssertExtensions 10 | { 11 | public static void ShouldHaveCount(this IEnumerable collection, int expectedCount) 12 | { 13 | var actual = collection.Cast().Count(); 14 | Assert.Equal(expectedCount, actual); 15 | } 16 | 17 | public static void ShouldBeEqualTo(this T target, T expectedValue) 18 | { 19 | Assert.Equal(expectedValue, target); 20 | } 21 | 22 | public static void ShouldBeTrue(this bool target) 23 | { 24 | Assert.True(target); 25 | } 26 | 27 | public static void ShouldBeTrue(this bool target, string message) 28 | { 29 | Assert.True(target, message); 30 | } 31 | 32 | public static void ShouldBeFalse(this bool target) 33 | { 34 | Assert.False(target); 35 | } 36 | 37 | public static void ShouldContain(this IEnumerable target, Func predicate) 38 | { 39 | Assert.Collection(target, item => Assert.True(predicate(item))); 40 | } 41 | 42 | public static void ShouldContain(this string target, string piece) 43 | { 44 | target.Contains(piece).ShouldBeTrue(); 45 | } 46 | 47 | public static void ShouldNotBeNull(this T target) where T : class 48 | { 49 | Assert.NotNull(target); 50 | } 51 | 52 | public static void ShouldBeNull(this T target) where T : class 53 | { 54 | Assert.Null(target); 55 | } 56 | 57 | public static void ShouldBeOfType(this object target) 58 | { 59 | Assert.IsAssignableFrom(target); 60 | } 61 | 62 | public static T Throws(this Action action) where T : Exception 63 | { 64 | return Assert.Throws(action); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /MemBus.Tests/Help/FakeBus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive; 3 | using System.Threading.Tasks; 4 | using MemBus.Subscribing; 5 | 6 | namespace MemBus.Tests.Help 7 | { 8 | public class FakeBus : IBus 9 | { 10 | public void Publish(object message) 11 | { 12 | Message = message; 13 | } 14 | 15 | public IDisposable Publish(IObservable observable) 16 | { 17 | return this; 18 | } 19 | 20 | public Task PublishAsync(object message) 21 | { 22 | return new Task(() => Message = message); 23 | } 24 | 25 | public object Message { get; set; } 26 | 27 | public void VerifyMessageIsOfType() 28 | { 29 | Message.ShouldNotBeNull(); 30 | Message.ShouldBeOfType(); 31 | } 32 | 33 | void IDisposable.Dispose() 34 | { 35 | 36 | } 37 | 38 | public IDisposable Subscribe(Action subscription) 39 | { 40 | return this; 41 | } 42 | 43 | public IDisposable Subscribe(Action subscription, ISubscriptionShaper shaper) 44 | { 45 | return this; 46 | } 47 | 48 | public IDisposable Subscribe(object subscriber) 49 | { 50 | return this; 51 | } 52 | 53 | public IObservable Observe() 54 | { 55 | return new AnonymousObservable(observer => this); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /MemBus.Tests/Help/Helpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MemBus.Publishing; 3 | using MemBus.Setup; 4 | using MemBus.Subscribing; 5 | using Membus.Tests.Help; 6 | 7 | namespace MemBus.Tests.Help 8 | { 9 | internal static class Helpers 10 | { 11 | 12 | public static ISubscription MockSubscriptionThatHandles() 13 | { 14 | return new SubscriptionThatFakesHandles(); 15 | } 16 | 17 | public static PublishChainCasing Configure(this PublishChainCasing pipeline, Action configure) 18 | { 19 | configure(pipeline); 20 | return pipeline; 21 | } 22 | 23 | #if !NETCORE 24 | public static System.Windows.Threading.DispatcherSynchronizationContext CreateDispatchContext() 25 | { 26 | var syncContext = new System.Windows.Threading.DispatcherSynchronizationContext( 27 | System.Windows.Threading.Dispatcher.CurrentDispatcher); 28 | System.Threading.SynchronizationContext.SetSynchronizationContext(syncContext); 29 | return syncContext; 30 | } 31 | #endif 32 | 33 | public static MessageEndpointsBuilder MakeBuilder(this IMethodInfoScanner scanner) 34 | { 35 | var b = new MessageEndpointsBuilder(); 36 | b.AddScanner(scanner); 37 | return b; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /MemBus.Tests/Help/IClassicIHandleStuffI.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MemBus.Tests.Help 4 | { 5 | public interface IClassicIHandleStuffI 6 | { 7 | void Gimme(T theThing); 8 | } 9 | 10 | public interface IWeirdHandler 11 | { 12 | void Handle(T msg); 13 | IObservable Producer(); 14 | } 15 | 16 | public interface IInvalidHandlerInterfaceBecauseNoParameter 17 | { 18 | void Gimme(); 19 | } 20 | 21 | class InvalidHandlerInterfaceBecauseNoParameter : IInvalidHandlerInterfaceBecauseNoParameter 22 | { 23 | public void Gimme() { } 24 | } 25 | 26 | public interface IInvalidHandlerInterfaceBecauseTwoMethodsOfrequestedPattern 27 | { 28 | void Gimme(object thing); 29 | void Gamme(object thang); 30 | } 31 | 32 | class InvalidHandlerInterfaceBecauseTwoMethodsOfrequestedPattern : IInvalidHandlerInterfaceBecauseTwoMethodsOfrequestedPattern 33 | { 34 | public void Gimme(object thing){ } 35 | 36 | public void Gamme(object thang) { } 37 | } 38 | 39 | public interface IInvalidHandlerInterfaceBecauseTwoParams 40 | { 41 | void Gimme(object thing, object thang); 42 | } 43 | 44 | class InvalidHandlerInterfaceBecauseTwoParams : IInvalidHandlerInterfaceBecauseTwoParams 45 | { 46 | public void Gimme(object thing, object thang) { } 47 | } 48 | 49 | public interface IInvalidHandlerInterfaceBecauseReturnType 50 | { 51 | int Gimme(object thing); 52 | } 53 | 54 | class InvalidHandlerInterfaceBecauseReturnType : IInvalidHandlerInterfaceBecauseReturnType 55 | { 56 | public int Gimme(object thing) { return 0; } 57 | } 58 | } -------------------------------------------------------------------------------- /MemBus.Tests/Help/ItfNonGenericForHandles.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MemBus.Tests.Help 4 | { 5 | public interface ItfNonGenericForHandles 6 | { 7 | void Handle(MessageA msg); 8 | } 9 | 10 | class AHandlerThroughSimpleInterface : ItfNonGenericForHandles 11 | { 12 | public int MsgCount; 13 | 14 | public void Handle(MessageA msg) 15 | { 16 | MsgCount++; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /MemBus.Tests/Help/MessageA.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MemBus.Tests.Help 4 | { 5 | public class MessageA 6 | { 7 | public string Name { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /MemBus.Tests/Help/Messages.cs: -------------------------------------------------------------------------------- 1 | using Membus.Tests.Help; 2 | 3 | namespace MemBus.Tests.Help 4 | { 5 | 6 | public class MessageB 7 | { 8 | public string Id { get; set; } 9 | } 10 | 11 | public class MessageC 12 | { 13 | } 14 | 15 | public class MessageASpecialization : MessageA 16 | { 17 | } 18 | 19 | public class Transport 20 | { 21 | public bool On; 22 | } 23 | } -------------------------------------------------------------------------------- /MemBus.Tests/Help/MockSubscription.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using MemBus.Subscribing; 4 | using MemBus.Support; 5 | using Membus.Tests.Help; 6 | 7 | namespace MemBus.Tests.Help 8 | { 9 | public class MockSubscription : IDisposableSubscription, IDisposable 10 | { 11 | private readonly ManualResetEvent _evtBlock; 12 | private readonly ManualResetEvent _evtSignal; 13 | public int Received; 14 | 15 | public MockSubscription() 16 | { 17 | } 18 | 19 | public MockSubscription(ManualResetEvent evtBlock = null, ManualResetEvent evtSignal = null) 20 | { 21 | _evtBlock = evtBlock; 22 | _evtSignal = evtSignal; 23 | } 24 | 25 | public void Push(object message) 26 | { 27 | if (_evtBlock != null) 28 | _evtBlock.WaitOne(); 29 | Received++; 30 | if (_evtSignal != null) 31 | _evtSignal.Set(); 32 | } 33 | 34 | public bool Handles(Type messageType) 35 | { 36 | return messageType.CanBeCastTo(); 37 | } 38 | 39 | public IDisposable GetDisposer() 40 | { 41 | return this; 42 | } 43 | 44 | public bool IsDisposed 45 | { 46 | get; private set; 47 | } 48 | 49 | public event EventHandler Disposed; 50 | 51 | public void Dispose() 52 | { 53 | IsDisposed = true; 54 | Disposed.Raise(this); 55 | } 56 | } 57 | 58 | public class DenyingSubscription : ISubscription, IDenyShaper 59 | { 60 | public void Push(object message) 61 | { 62 | 63 | } 64 | 65 | public bool Handles(Type messageType) 66 | { 67 | return messageType.Equals(typeof (MessageA)); 68 | } 69 | 70 | 71 | public bool Deny 72 | { 73 | get { return true; } 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /MemBus.Tests/Help/NamedSubscription.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MemBus.Tests.Help 4 | { 5 | public class NamedSubscription : ISubscription 6 | { 7 | private readonly string _name; 8 | private readonly Action _action; 9 | private readonly ISubscription _inner; 10 | 11 | public NamedSubscription(string name, Action action, ISubscription inner) 12 | { 13 | _name = name; 14 | _action = action; 15 | _inner = inner; 16 | } 17 | 18 | public NamedSubscription(string name, ISubscription inner) : this(name, null, inner) 19 | { 20 | } 21 | 22 | public ISubscription Inner 23 | { 24 | get { return _inner; } 25 | } 26 | 27 | public string Name 28 | { 29 | get { return _name; } 30 | } 31 | 32 | public void Push(object message) 33 | { 34 | if (_action != null) 35 | _action(); 36 | _inner.Push(message); 37 | Pushed++; 38 | } 39 | 40 | public bool Handles(Type messageType) 41 | { 42 | return _inner.Handles(messageType); 43 | } 44 | 45 | protected int Pushed { get; private set; } 46 | 47 | 48 | } 49 | } -------------------------------------------------------------------------------- /MemBus.Tests/Help/PublishPipelineTester.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MemBus.Publishing; 3 | using MemBus.Setup; 4 | 5 | namespace MemBus.Tests.Help 6 | { 7 | public class PublishPipelineTester where T : new() 8 | { 9 | 10 | public FakePublishPipelineMember Mock1 { get; private set; } 11 | public IPublishPipelineMember Mock1Object { get { return Mock1; } } 12 | public FakePublishPipelineMember Mock2 { get; private set; } 13 | public IPublishPipelineMember Mock2Object { get { return Mock2; } } 14 | public FakePublishPipelineMember Mock3 { get; private set; } 15 | public IPublishPipelineMember Mock3Object { get { return Mock3; } } 16 | 17 | public PublishPipelineTester() 18 | { 19 | Mock1 = new FakePublishPipelineMember(); 20 | Mock2 = new FakePublishPipelineMember(); 21 | Mock3 = new FakePublishPipelineMember(); 22 | } 23 | 24 | public PublishPipelineTester TestWith(Action configuration) 25 | { 26 | pipelineSkeleton(new T(), configuration); 27 | return this; 28 | } 29 | 30 | private static void pipelineSkeleton(object message, Action configuration) 31 | { 32 | var p = new PublishChainCasing(null).Configure(configuration); 33 | var token = new PublishToken(message, new ISubscription[] { }); 34 | p.LookAt(token); 35 | } 36 | 37 | } 38 | 39 | public class FakePublishPipelineMember : IPublishPipelineMember 40 | { 41 | private bool _cancelToken; 42 | 43 | public void LookAt(PublishToken token) 44 | { 45 | Token = token; 46 | if (_cancelToken) 47 | Token.Cancel = true; 48 | } 49 | 50 | public void VerifyNotCalled() 51 | { 52 | Token.ShouldBeNull(); 53 | } 54 | 55 | public void VerifyCalled() 56 | { 57 | Token.ShouldNotBeNull(); 58 | } 59 | 60 | public PublishToken Token { get; set; } 61 | 62 | internal void CancelTokenWhenSeen() 63 | { 64 | _cancelToken = true; 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /MemBus.Tests/Help/RxBasedFooObservable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Linq; 3 | 4 | namespace MemBus.Tests.Help 5 | { 6 | public class RxBasedFooObservable : RxEnabledObservable 7 | { 8 | public RxBasedFooObservable(IBus bus) : base(bus) 9 | { 10 | } 11 | 12 | protected override IObservable constructObservable(IObservable startingPoint) 13 | { 14 | return startingPoint.Where(msgA => msgA.Name == "Foo"); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /MemBus.Tests/Help/SampleClass.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MemBus.Tests.Help 4 | { 5 | public class SampleClass 6 | { 7 | #pragma warning disable 0067 8 | public event EventHandler MyEvent; 9 | 10 | public string HelloEntry; 11 | 12 | public string Name { get; set; } 13 | 14 | // ReSharper disable once UnusedAutoPropertyAccessor.Local 15 | public DateTime Birthdate { get; private set; } 16 | 17 | public bool AValue { get; set; } 18 | 19 | public bool Hello(string bla) 20 | { 21 | HelloEntry = bla; 22 | return true; 23 | } 24 | 25 | public void SetDate(DateTime date) 26 | { 27 | 28 | } 29 | 30 | public void SetFoo(IFoo foo) 31 | { 32 | 33 | } 34 | } 35 | 36 | public interface IFoo 37 | { 38 | } 39 | 40 | class Foo : IFoo 41 | { 42 | } 43 | } -------------------------------------------------------------------------------- /MemBus.Tests/Help/SimpleResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using MemBus.Support; 6 | 7 | namespace MemBus.Tests.Help 8 | { 9 | public class SimpleResolver : ISubscriptionResolver, IRequireServices, IEnumerable 10 | { 11 | private readonly List _subscriptions = new List(); 12 | 13 | public IServices Services { get; private set; } 14 | 15 | public void AddServices(IServices svc) 16 | { 17 | Services = svc; 18 | } 19 | public IEnumerable GetSubscriptionsFor(object message) 20 | { 21 | if (message == null) 22 | throw new ArgumentNullException("message"); 23 | 24 | return _subscriptions.Where(s => s.Handles(message.GetType())); 25 | } 26 | 27 | public bool Add(ISubscription s) 28 | { 29 | _subscriptions.Add(s); 30 | return true; 31 | } 32 | 33 | public IEnumerator GetEnumerator() 34 | { 35 | return _subscriptions.GetEnumerator(); 36 | } 37 | 38 | IEnumerator IEnumerable.GetEnumerator() 39 | { 40 | return GetEnumerator(); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /MemBus.Tests/Help/SomeCrazyHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MemBus.Tests.Help 4 | { 5 | public class SomeCrazyHandler : IClassicIHandleStuffI, IClassicIHandleStuffI 6 | { 7 | public int MessageCCount; 8 | public int MessageACount; 9 | public int MessageBCount; 10 | public int MsgSpecialACount; 11 | 12 | public void Gimme(MessageC theThing) 13 | { 14 | MessageCCount++; 15 | } 16 | 17 | public void Gimme(MessageB theThing) 18 | { 19 | MessageBCount++; 20 | } 21 | 22 | public void Handle(MessageA msg) 23 | { 24 | MessageACount++; 25 | } 26 | 27 | public void Handle(MessageB msg) 28 | { 29 | MessageBCount++; 30 | } 31 | 32 | public void Schmandle(MessageASpecialization msg) 33 | { 34 | MsgSpecialACount++; 35 | } 36 | } 37 | 38 | public class HandlerWithExplicitImpl : IClassicIHandleStuffI 39 | { 40 | public int MessageCount; 41 | 42 | void IClassicIHandleStuffI.Gimme(MessageC theThing) 43 | { 44 | MessageCount++; 45 | } 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /MemBus.Tests/Help/SomeHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Reactive.Linq; 4 | using MemBus.Subscribing; 5 | 6 | namespace MemBus.Tests.Help 7 | { 8 | public class SomeHandler : IAcceptDisposeToken, IWeirdHandler 9 | { 10 | public int MsgACalls; 11 | public int MsgBCalls; 12 | public int MsgCCalls; 13 | 14 | public readonly MessageC MsgC = new MessageC(); 15 | 16 | private IDisposable _disposeToken; 17 | 18 | public void Handle(MessageA msg) 19 | { 20 | MsgACalls++; 21 | } 22 | 23 | IObservable IWeirdHandler.Producer() 24 | { 25 | return Observable.Return(new MessageA()); 26 | } 27 | 28 | public MessageC Route(MessageB msg) 29 | { 30 | MsgBCalls++; 31 | return MsgC; 32 | } 33 | 34 | public void SomeOtherMethod(MessageC msg) 35 | { 36 | MsgCCalls++; 37 | } 38 | 39 | public void InvokeDisposeToken() 40 | { 41 | if (_disposeToken != null) 42 | _disposeToken.Dispose(); 43 | } 44 | 45 | public void Accept(IDisposable disposeToken) 46 | { 47 | _disposeToken = disposeToken; 48 | } 49 | } 50 | 51 | public class HandlerWithRouteReturningPrimitive 52 | { 53 | public int MsgCall; 54 | 55 | public int Route(string msg) 56 | { 57 | MsgCall++; 58 | return MsgCall; 59 | } 60 | } 61 | 62 | public class HandlerReturningNull 63 | { 64 | public int MsgCall; 65 | 66 | public object Route(string msg) 67 | { 68 | MsgCall++; 69 | return null; 70 | } 71 | } 72 | 73 | public class EnumeratingHandler 74 | { 75 | public IEnumerable Route(MessageA msg) 76 | { 77 | yield return new MessageB(); 78 | yield return new MessageC(); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /MemBus.Tests/Help/SubscriptionThatFakesHandles.cs: -------------------------------------------------------------------------------- 1 | using MemBus; 2 | using System; 3 | 4 | namespace Membus.Tests.Help 5 | { 6 | public class SubscriptionThatFakesHandles : ISubscription 7 | { 8 | public int PushCalls; 9 | 10 | public bool Handles(Type messageType) 11 | { 12 | return messageType == typeof(T); 13 | } 14 | 15 | void ISubscription.Push(object message) 16 | { 17 | PushCalls++; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /MemBus.Tests/Help/TestShaper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MemBus.Subscribing; 3 | using MemBus.Support; 4 | 5 | namespace MemBus.Tests.Help 6 | { 7 | public class TestShaper : ISubscriptionShaper, IRequireServices 8 | { 9 | private readonly string name; 10 | private readonly Action actionCalledOnPublish; 11 | 12 | public TestShaper(string name, Action actionCalledOnPublish) 13 | { 14 | this.name = name; 15 | this.actionCalledOnPublish = actionCalledOnPublish; 16 | } 17 | 18 | public TestShaper(string name) 19 | : this(name, null) 20 | { 21 | this.name = name; 22 | } 23 | 24 | public IServices Services { get; private set; } 25 | 26 | public void AddServices(IServices svc) { 27 | Services = svc; 28 | } 29 | 30 | public ISubscription EnhanceSubscription(ISubscription subscription) 31 | { 32 | return new NamedSubscription(name, actionCalledOnPublish, subscription); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /MemBus.Tests/Integration/UnexpectedDisposalOfSubscriptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MemBus.Configurators; 3 | using MemBus.Subscribing; 4 | using MemBus.Tests.Help; 5 | using Xunit; 6 | 7 | namespace MemBus.Tests.Integration 8 | { 9 | internal class Subscriber : IAcceptDisposeToken, IDisposable 10 | { 11 | private IDisposable _disposeToken; 12 | private IDisposable _other; 13 | private bool _disposing; 14 | 15 | public bool GotMessage { get; private set; } 16 | 17 | public void Handle(string msg) 18 | { 19 | // A blunt way to remove a subscription during message handling 20 | DisposeOther(); 21 | GotMessage = true; 22 | } 23 | 24 | public void Aquaint(IDisposable d){ _other = d; } 25 | 26 | public void Dispose() 27 | { 28 | if (_disposing) return; 29 | _disposing = true; 30 | _disposeToken.Dispose(); 31 | DisposeOther(); 32 | _disposing = false; 33 | } 34 | 35 | private void DisposeOther() { if (_other != null) _other.Dispose(); } 36 | 37 | void IAcceptDisposeToken.Accept(IDisposable disposeToken) { _disposeToken = disposeToken; } 38 | } 39 | 40 | public class UnexpectedDisposalOfSubscriptions 41 | { 42 | private readonly IBus _bus; 43 | private readonly Subscriber _partnerInCrime1; 44 | private readonly Subscriber _partnerInCrime2; 45 | 46 | public UnexpectedDisposalOfSubscriptions() 47 | { 48 | _bus = BusSetup.StartWith() 49 | .Apply(adp => adp.RegisterMethods("Handle")) 50 | .Construct(); 51 | _bus.Subscribe(_partnerInCrime1 = new Subscriber()); 52 | _bus.Subscribe(_partnerInCrime2 = new Subscriber()); 53 | 54 | // One disposes the other. That way we keep independent of call sequence 55 | _partnerInCrime1.Aquaint(_partnerInCrime2); 56 | _partnerInCrime2.Aquaint(_partnerInCrime1); 57 | } 58 | 59 | [Fact] 60 | public void disposal_of_subscription_does_not_cause_exception_in_message_handling() 61 | { 62 | _bus.Publish("Boo!"); 63 | } 64 | 65 | [Fact] 66 | public void one_subscription_got_the_message() 67 | { 68 | _bus.Publish("Boo!"); 69 | (_partnerInCrime1.GotMessage || _partnerInCrime2.GotMessage).ShouldBeTrue(); 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /MemBus.Tests/Integration/When_using_disposable_buses.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MemBus.Configurators; 3 | using MemBus.Tests.Help; 4 | using Xunit; 5 | 6 | namespace MemBus.Tests.Integration 7 | { 8 | 9 | public class When_Using_Disposable_Buses 10 | { 11 | [Fact] 12 | public void A_disposed_bus_throws() 13 | { 14 | var bus = BusSetup.StartWith().Construct(); 15 | bus.Dispose(); 16 | (new Action(() => bus.Publish(new MessageA()))).Throws(); 17 | (new Action(() => bus.Subscribe(msg=>{}))).Throws(); 18 | (new Action(() => bus.Observe())).Throws(); 19 | } 20 | 21 | } 22 | } -------------------------------------------------------------------------------- /MemBus.Tests/Integration/When_using_the_bus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Linq; 3 | using System.Threading; 4 | using MemBus.Configurators; 5 | using MemBus.Messages; 6 | using MemBus.Tests.Help; 7 | using Membus.Tests.Help; 8 | 9 | using Xunit; 10 | 11 | namespace MemBus.Tests.Integration 12 | { 13 | 14 | public class When_Using_The_Bus 15 | { 16 | [Fact] 17 | public void Default_setup_routes_the_message_correctly() 18 | { 19 | var sub = new SubscriptionThatFakesHandles(); 20 | 21 | var b = BusSetup 22 | .StartWith(cb => cb.AddSubscription(sub)) 23 | .Construct(); 24 | var messageA = new MessageA(); 25 | b.Publish(messageA); 26 | sub.PushCalls.ShouldBeEqualTo(1); 27 | } 28 | 29 | [Fact] 30 | public void Default_setup_provides_subscription_shape() 31 | { 32 | var received = 0; 33 | var b = BusSetup.StartWith().Construct(); 34 | using (b.Subscribe(msg => received++)) 35 | { 36 | b.Publish(new MessageA()); 37 | received.ShouldBeEqualTo(1); 38 | } 39 | } 40 | 41 | [Fact] 42 | public void Resolvers_will_get_access_to_services() 43 | { 44 | var simpleResolver = new SimpleResolver(); 45 | using (BusSetup.StartWith(cb => cb.AddResolver(simpleResolver)).Construct()) 46 | { 47 | simpleResolver.Services.ShouldNotBeNull(); 48 | } 49 | } 50 | 51 | 52 | [Fact] 53 | public void Subscription_with_filtering_works() 54 | { 55 | var received = 0; 56 | var b = BusSetup.StartWith().Construct(); 57 | using (b.Observe().Where(msg => msg.Id == "A").Subscribe(msg => received++)) 58 | { 59 | b.Publish(new MessageB { Id = "A" }); 60 | b.Publish(new MessageB { Id = "B" }); 61 | } 62 | received.ShouldBeEqualTo(1); 63 | } 64 | 65 | 66 | [Fact] 67 | public void Exceptions_are_made_available_as_messages() 68 | { 69 | var evt = new ManualResetEvent(false); 70 | ExceptionOccurred capturedMessage = null; 71 | var bus = BusSetup.StartWith().Construct(); 72 | bus.Subscribe(msg => 73 | { 74 | throw new ArgumentException("Bad message"); 75 | }); 76 | bus.Subscribe(x => 77 | { 78 | capturedMessage = x; 79 | evt.Set(); 80 | }); 81 | 82 | bus.Publish(new MessageB()); 83 | var signaled = evt.WaitOne(TimeSpan.FromSeconds(2)); 84 | if (!signaled) 85 | Assert.True(false, "Exception was never captured!"); 86 | 87 | capturedMessage.Exception.ShouldBeOfType(); 88 | var xes = ((AggregateException) capturedMessage.Exception).InnerExceptions; 89 | xes[0].ShouldBeOfType(); 90 | xes[0].Message.ShouldBeEqualTo("Bad message"); 91 | } 92 | 93 | [Fact] 94 | public void A_disposed_subscription_is_gone() 95 | { 96 | int received = 0; 97 | var bus = BusSetup.StartWith().Construct(); 98 | var d = bus.Subscribe(msg => received++); 99 | bus.Publish(new MessageA()); 100 | received.ShouldBeEqualTo(1); 101 | d.Dispose(); 102 | bus.Publish(new MessageA()); 103 | received.ShouldBeEqualTo(1); 104 | } 105 | 106 | } 107 | } -------------------------------------------------------------------------------- /MemBus.Tests/Integration/When_using_the_bus_in_the_ui.cs: -------------------------------------------------------------------------------- 1 | #if !NETCORE 2 | using System.Reactive.Linq; 3 | using System; 4 | using System.Threading; 5 | using System.Windows.Threading; 6 | using MemBus.Configurators; 7 | using MemBus.Tests.Help; 8 | 9 | using Xunit; 10 | 11 | namespace MemBus.Tests.Integration 12 | { 13 | public class When_Using_The_Bus_In_The_Ui 14 | { 15 | 16 | [Fact(Skip ="Wait...")] 17 | public void Subscription_push_can_be_dispatched_on_designated_thread_blocking_scenario() 18 | { 19 | var threadId = -2; 20 | var threadIdFromTest = -1; 21 | IBus bus = null; 22 | 23 | var resetEvent = new ManualResetEvent(false); 24 | 25 | var uiThread = new Thread( 26 | () => 27 | { 28 | var sync = Helpers.CreateDispatchContext(); 29 | var frame = new DispatcherFrame(); 30 | threadId = Thread.CurrentThread.ManagedThreadId; 31 | bus = BusSetup.StartWith().Construct(); 32 | bus.Observe().ObserveOn(sync).Subscribe(msg => 33 | { 34 | threadIdFromTest = Thread.CurrentThread.ManagedThreadId; 35 | frame.Continue = false; 36 | }); 37 | resetEvent.Set(); 38 | Dispatcher.PushFrame(frame); 39 | }); 40 | uiThread.Start(); 41 | resetEvent.WaitOne(); 42 | bus.Publish(new MessageB()); 43 | uiThread.Join(); 44 | threadIdFromTest.ShouldBeEqualTo(threadId); 45 | } 46 | 47 | [Fact] 48 | public void Subscription_push_can_be_dispatched_on_designated_thread_async_scenario() 49 | { 50 | var threadId = Thread.CurrentThread.ManagedThreadId; 51 | var threadIdFromTest = -1; 52 | var sync = Helpers.CreateDispatchContext(); 53 | 54 | var frame = new DispatcherFrame(); 55 | var bus = BusSetup.StartWith().Construct(); 56 | bus.Observe().ObserveOn(sync).Subscribe( 57 | msg => 58 | { 59 | threadIdFromTest = Thread.CurrentThread.ManagedThreadId; 60 | frame.Continue = false; 61 | }); 62 | bus.Publish(new MessageB()); 63 | Dispatcher.PushFrame(frame); 64 | threadIdFromTest.ShouldBeEqualTo(threadId); 65 | } 66 | 67 | } 68 | } 69 | #endif -------------------------------------------------------------------------------- /MemBus.Tests/Integration/When_using_the_bus_with_flexible_subscriptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MemBus.Configurators; 3 | using MemBus.Subscribing; 4 | using MemBus.Tests.Help; 5 | using System.Linq; 6 | 7 | using Xunit; 8 | 9 | namespace MemBus.Tests.Integration 10 | { 11 | 12 | public class When_Using_The_Bus_With_Flexible_Subscriptions 13 | { 14 | private IBus _bus; 15 | private SomeHandler _handler; 16 | private IDisposable _handlerDisposer; 17 | 18 | public When_Using_The_Bus_With_Flexible_Subscriptions() 19 | { 20 | _bus = ConstructBusForHandle(); 21 | _handler = new SomeHandler(); 22 | _handlerDisposer = _bus.Subscribe(_handler); 23 | } 24 | 25 | [Fact] 26 | public void Without_flexibility_setup_subscribing_instance_throws() 27 | { 28 | var b = BusSetup.StartWith().Construct(); 29 | new Action(() => b.Subscribe(new SomeHandler())).Throws(); 30 | } 31 | 32 | [Fact] 33 | public void Basic_publishing_reaches_the_flexible_subscription() 34 | { 35 | _bus.Publish(new MessageA()); 36 | _handler.MsgACalls.ShouldBeEqualTo(1); 37 | } 38 | 39 | [Fact] 40 | public void Disposal_also_works_on_flexible_subscriptions() 41 | { 42 | _bus.Publish(new MessageA()); 43 | _handlerDisposer.Dispose(); 44 | _bus.Publish(new MessageA()); 45 | _handler.MsgACalls.ShouldBeEqualTo(1); 46 | } 47 | 48 | [Fact] 49 | public void Handler_may_accept_its_own_dispose_token() 50 | { 51 | _bus.Publish(new MessageA()); 52 | _handler.MsgACalls.ShouldBeEqualTo(1); 53 | _handler.InvokeDisposeToken(); 54 | _bus.Publish(new MessageA()); 55 | _handler.MsgACalls.ShouldBeEqualTo(1); 56 | } 57 | 58 | [Fact] 59 | public void Related_to_caching_resolver_failed_publish() 60 | { 61 | _bus.Publish(new MessageA()); 62 | _handler.MsgACalls.ShouldBeEqualTo(1); 63 | _handler.InvokeDisposeToken(); 64 | 65 | _handler = new SomeHandler(); 66 | _handler.MsgACalls.ShouldBeEqualTo(0); 67 | _bus.Subscribe(_handler); 68 | 69 | _bus.Publish(new MessageB()); 70 | _bus.Publish(new MessageA()); 71 | 72 | _handler.MsgACalls.ShouldBeEqualTo(1); 73 | } 74 | 75 | [Fact] 76 | public void method_with_return_type_can_be_treated_as_subscription() 77 | { 78 | _bus.Publish(new MessageB()); 79 | _handler.MsgBCalls.ShouldBeEqualTo(1); 80 | } 81 | 82 | [Fact] 83 | public void return_type_of_a_subscription_is_treated_as_message() 84 | { 85 | MessageC msgC = null; 86 | _bus.Subscribe((MessageC msg) => msgC = msg); 87 | _bus.Publish(new MessageB()); 88 | 89 | msgC.ShouldNotBeNull(); 90 | msgC.ShouldBeEqualTo(_handler.MsgC); 91 | } 92 | 93 | 94 | [Fact] 95 | public void returning_null_is_tolerated() 96 | { 97 | var h = new HandlerReturningNull(); 98 | using (_bus.Subscribe(h)) 99 | { 100 | _bus.Publish("Causing a null being returned from a route"); 101 | h.MsgCall.ShouldBeEqualTo(1); 102 | } 103 | } 104 | 105 | [Fact] 106 | public void return_type_enumerable_publishes_every_item_as_message() 107 | { 108 | var msgB = 0; 109 | var msgC = 0; 110 | _bus.Subscribe(new EnumeratingHandler()); 111 | _bus.Subscribe((MessageB m) => msgB++); 112 | _bus.Subscribe((MessageB m) => msgC++); 113 | _bus.Publish(new MessageA()); 114 | 115 | msgB.ShouldBeEqualTo(1); 116 | msgC.ShouldBeEqualTo(1); 117 | } 118 | 119 | [Fact] 120 | public void void_method_subscription_correct_returns_known_instance() 121 | { 122 | var h = new SomeHandler(); 123 | var sb = new MethodScanner("Handle").MakeBuilder(); 124 | var subs = sb.BuildSubscriptions(h).OfType().ToList(); 125 | subs.ShouldHaveCount(1); 126 | subs.All(s => s.Instance.Equals(h)).ShouldBeTrue("Not all known instances are the correct one"); 127 | } 128 | 129 | [Fact] 130 | public void publishing_method_subscription_correct_returns_known_instance() 131 | { 132 | var h = new SomeHandler(); 133 | var sb = new MethodScanner("Route").MakeBuilder(); 134 | var subs = sb.BuildSubscriptions(h).OfType().ToList(); 135 | subs.ShouldHaveCount(1); 136 | subs.All(s => s.Instance.Equals(h)).ShouldBeTrue("Not all known instances are the correct one"); 137 | } 138 | 139 | private static IBus ConstructBusForHandle() 140 | { 141 | return BusSetup.StartWith() 142 | .Apply(c => c.RegisterMethods("Handle").RegisterMethods("Route")).Construct(); 143 | } 144 | } 145 | } -------------------------------------------------------------------------------- /MemBus.Tests/MemBus.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | portable 4 | MemBus.Tests 5 | MemBus.Tests 6 | true 7 | false 8 | false 9 | false 10 | false 11 | false 12 | false 13 | false 14 | false 15 | net46;netcoreapp2.0 16 | 7.2 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | $(DefineConstants);NETCORE 36 | 37 | -------------------------------------------------------------------------------- /MemBus.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | [assembly: AssemblyTitle("MemBus.Tests")] 4 | [assembly: AssemblyDescription("")] 5 | [assembly: AssemblyConfiguration("")] 6 | [assembly: AssemblyCompany("realfiction")] 7 | [assembly: AssemblyProduct("MemBus.Tests")] 8 | [assembly: AssemblyCopyright("Copyright © realfiction 2013")] 9 | [assembly: AssemblyTrademark("")] 10 | [assembly: AssemblyCulture("")] 11 | [assembly: AssemblyVersion("1.0.0.0")] 12 | [assembly: AssemblyFileVersion("1.0.0.0")] 13 | -------------------------------------------------------------------------------- /MemBus.Tests/Publishing/Awaitable_Publish.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using MemBus.Configurators; 3 | using Xunit; 4 | 5 | namespace MemBus.Tests.Publishing 6 | { 7 | public class Awaitable_Publish 8 | { 9 | [Fact] 10 | public async Task using_the_awaitable_publish() 11 | { 12 | var b = BusSetup.StartWith().Construct(); 13 | var messageReceived = false; 14 | b.Subscribe((string h) => messageReceived = true); 15 | await b.PublishAsync("Hello"); 16 | Assert.True(messageReceived); 17 | } 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /MemBus.Tests/Publishing/Using_Publish_Pipeline.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using MemBus.Configurators; 3 | using MemBus.Publishing; 4 | using MemBus.Support; 5 | using MemBus.Tests.Help; 6 | using System.Linq; 7 | 8 | using Xunit; 9 | 10 | namespace MemBus.Tests.Publishing 11 | { 12 | public class Using_Publish_Pipeline 13 | { 14 | [Fact] 15 | public void publishes_message_parallel() 16 | { 17 | var p = new ParallelBlockingPublisher(); 18 | PublisherCheck(p); 19 | } 20 | 21 | [Fact] 22 | public void publishes_message_sequentially() 23 | { 24 | var p = new SequentialPublisher(); 25 | PublisherCheck(p); 26 | } 27 | 28 | private static void PublisherCheck(IPublishPipelineMember p) 29 | { 30 | var token = new PublishToken(new MessageA(), new[] { new MockSubscription(), new MockSubscription() }); 31 | p.LookAt(token); 32 | token.Subscriptions.OfType>().All(s=>s.Received == 1).ShouldBeTrue(); 33 | } 34 | 35 | [Fact] 36 | public void publishes_message_fire_and_forget() 37 | { 38 | var p = new ParallelNonBlockingPublisher(); 39 | var evtBlock = new ManualResetEvent(false); 40 | var evtSignal = new ManualResetEvent(false); 41 | var evtSignal2 = new ManualResetEvent(false); 42 | var lockingSub = new MockSubscription(evtBlock, evtSignal); 43 | var runThroughSub = new MockSubscription(evtSignal:evtSignal2); 44 | 45 | var token = new PublishToken(new MessageA(), new[] { lockingSub, runThroughSub }); 46 | p.LookAt(token); 47 | lockingSub.Received.ShouldBeEqualTo(0); 48 | evtSignal2.WaitOne(); 49 | runThroughSub.Received.ShouldBeEqualTo(1); 50 | evtBlock.Set(); 51 | evtSignal.WaitOne(); 52 | lockingSub.Received.ShouldBeEqualTo(1); 53 | } 54 | 55 | [Fact] 56 | public void publish_pipeline_is_extensible() 57 | { 58 | var t = new PublishPipelineTester(); 59 | t.TestWith(pp => pp.DefaultPublishPipeline(t.Mock1Object, t.Mock2Object)); 60 | 61 | t.Mock1.VerifyCalled(); 62 | t.Mock2.VerifyCalled(); 63 | } 64 | 65 | [Fact] 66 | public void default_pubish_pipeline_is_replaceable() 67 | { 68 | var t = new PublishPipelineTester(); 69 | var b = BusSetup.StartWith() 70 | .Apply(cb => cb.ConfigurePublishing(cp => cp.DefaultPublishPipeline(t.Mock1Object))) 71 | .Construct(); 72 | 73 | b.Publish(new MessageB()); 74 | 75 | t.Mock1.VerifyCalled(); 76 | 77 | } 78 | 79 | [Fact] 80 | public void non_default_publish_pipeline_takes_precedence() 81 | { 82 | var t = new PublishPipelineTester(); 83 | t.TestWith( 84 | pp => 85 | { 86 | pp.DefaultPublishPipeline(t.Mock1Object, t.Mock2Object); 87 | pp.MessageMatch(mi => mi.IsType()).PublishPipeline(t.Mock2Object); 88 | }); 89 | 90 | 91 | t.Mock1.VerifyNotCalled(); 92 | t.Mock2.VerifyCalled(); 93 | } 94 | 95 | [Fact] 96 | public void Execution_of_pipeline_is_cancellable_by_member() 97 | { 98 | var t = new PublishPipelineTester(); 99 | t.Mock1.CancelTokenWhenSeen(); 100 | t.TestWith(pp => pp.DefaultPublishPipeline(t.Mock1Object, t.Mock2Object)); 101 | t.Mock1.VerifyCalled(); 102 | t.Mock2.VerifyNotCalled(); 103 | } 104 | 105 | [Fact] 106 | public void default_publish_pipeline_is_fallback() 107 | { 108 | var t = new PublishPipelineTester(); 109 | t.TestWith(pp => 110 | { 111 | pp.DefaultPublishPipeline(t.Mock1Object, t.Mock3Object); 112 | pp.MessageMatch(mi => mi.IsType()).PublishPipeline(t.Mock2Object); 113 | }); 114 | 115 | 116 | t.Mock1.VerifyCalled(); 117 | t.Mock2.VerifyNotCalled(); 118 | t.Mock3.VerifyCalled(); 119 | } 120 | 121 | [Fact] 122 | public void use_send_this_to_send_message_while_publishing() 123 | { 124 | var b = BusSetup.StartWith(cb=> cb.ConfigurePublishing( 125 | p => p.MessageMatch(m => m.IsType()).PublishPipeline(Publish.This(new MessageB()), new SequentialPublisher()))) 126 | .Construct(); 127 | 128 | int bCount = 0; 129 | int aCount = 0; 130 | b.Subscribe(msg => bCount++); 131 | b.Subscribe(msg => aCount++); 132 | 133 | b.Publish(new MessageA()); 134 | bCount.ShouldBeEqualTo(1); 135 | aCount.ShouldBeEqualTo(1); 136 | } 137 | 138 | [Fact] 139 | public void MessageInfo_isType_supports_variance() 140 | { 141 | var info = new MessageInfo(new Foo()); 142 | info.IsType().ShouldBeTrue(); 143 | } 144 | 145 | class Base { } 146 | 147 | class Foo : Base { } 148 | } 149 | } -------------------------------------------------------------------------------- /MemBus.Tests/Publishing/Using_publishing_methods.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using MemBus.Subscribing; 3 | using MemBus.Tests.Help; 4 | 5 | using Xunit; 6 | 7 | namespace MemBus.Tests.Publishing 8 | { 9 | public class UsingPublishingMethods 10 | { 11 | private MessageEndpointsBuilder _builder; 12 | private FakeBus _bus; 13 | 14 | public UsingPublishingMethods() 15 | { 16 | _bus = new FakeBus(); 17 | _builder = new MethodScanner("Route").MakeBuilder(); 18 | _builder.SetPublisher(_bus); 19 | } 20 | 21 | [Fact] 22 | public void Subscriptions_for_publishing_method_based_work_correctly() 23 | { 24 | var handler = new SomeHandler(); 25 | var subscription = _builder.BuildSubscriptions(handler).First(); 26 | subscription.Push(new MessageB()); 27 | 28 | subscription.Handles(typeof(MessageB)).ShouldBeTrue(); 29 | handler.MsgBCalls.ShouldBeEqualTo(1); 30 | _bus.VerifyMessageIsOfType(); 31 | } 32 | 33 | [Fact] 34 | public void primitive_type_is_used_as_return_value() 35 | { 36 | var handler = new HandlerWithRouteReturningPrimitive(); 37 | var subscription = _builder.BuildSubscriptions(handler).First(); 38 | 39 | subscription.Push("Hello"); 40 | 41 | subscription.Handles(typeof(string)).ShouldBeTrue(); 42 | handler.MsgCall.ShouldBeEqualTo(1); 43 | _bus.VerifyMessageIsOfType(); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /MemBus.Tests/Rx/Observable_As_Publish.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MemBus.Configurators; 3 | using MemBus.Tests.Help; 4 | using MemBus.Messages; 5 | using Xunit; 6 | 7 | namespace MemBus.Tests.Rx 8 | { 9 | 10 | public class ObservableAsPublish 11 | { 12 | private class DumbObservable : IObservable, IDisposable 13 | { 14 | private IObserver _observer; 15 | 16 | public IDisposable Subscribe(IObserver observer) 17 | { 18 | _observer = observer; 19 | return this; 20 | } 21 | 22 | public void Message(M msg) 23 | { 24 | _observer?.OnNext(msg); 25 | } 26 | 27 | public void Throw(Exception x) 28 | { 29 | _observer.OnError(x); 30 | } 31 | 32 | public void End() 33 | { 34 | _observer.OnCompleted(); 35 | } 36 | 37 | public void Dispose() 38 | { 39 | _observer.OnCompleted(); 40 | _observer = null; 41 | } 42 | } 43 | 44 | readonly IBus bus = BusSetup.StartWith().Construct(); 45 | private DumbObservable _dumbo; 46 | 47 | public ObservableAsPublish() 48 | { 49 | _dumbo = new DumbObservable(); 50 | bus.Publish(_dumbo); 51 | } 52 | 53 | [Fact] 54 | public void Messages_emanating_from_observable_are_received() 55 | { 56 | var messageReceived = false; 57 | using (bus.Subscribe((string s) => messageReceived = true)) 58 | { 59 | _dumbo.Message("Hi"); 60 | } 61 | messageReceived.ShouldBeTrue(); 62 | } 63 | 64 | [Fact] 65 | public void exception_is_mapped_to_correct_message() 66 | { 67 | var messageReceived = false; 68 | using (bus.Subscribe((ExceptionOccurred s) => messageReceived = true)) 69 | { 70 | _dumbo.Throw(new ArgumentException()); 71 | } 72 | messageReceived.ShouldBeTrue(); 73 | } 74 | 75 | [Fact] 76 | public void end_is_mapped_to_correct_message() 77 | { 78 | var messageReceived = false; 79 | using (bus.Subscribe((MessageStreamCompleted s) => messageReceived = true)) 80 | { 81 | _dumbo.End(); 82 | } 83 | messageReceived.ShouldBeTrue(); 84 | } 85 | 86 | } 87 | } 88 | 89 | -------------------------------------------------------------------------------- /MemBus.Tests/Rx/Observable_As_Timer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Concurrency; 3 | using System.Reactive.Linq; 4 | using System.Threading; 5 | using JetBrains.Annotations; 6 | using MemBus.Configurators; 7 | using MemBus.Subscribing; 8 | using MemBus.Tests.Help; 9 | using Xunit; 10 | 11 | namespace MemBus.Tests.Rx 12 | { 13 | public class ObservableAsTimer 14 | { 15 | readonly IBus _bus = BusSetup 16 | .StartWith() 17 | .Apply(cfg => cfg.RegisterMethods(info => true)) 18 | .Construct(); 19 | 20 | [Fact] 21 | public void Timer_from_observable_functionality() 22 | { 23 | var cd = new CountdownEvent(5); 24 | int messages = 0; 25 | _bus.Subscribe(new Timers()); 26 | 27 | using (_bus.Subscribe((MessageA _) => { 28 | cd.Signal(); 29 | Interlocked.Increment(ref messages); 30 | })) 31 | { 32 | var result = cd.Wait(TimeSpan.FromMilliseconds(1700)); 33 | if (!result) 34 | throw new ArgumentException("Timer did not complete"); 35 | Assert.Equal(5, messages); 36 | } 37 | } 38 | 39 | private class Timers 40 | { 41 | [UsedImplicitly] 42 | public IObservable ASignal() 43 | { 44 | return Observable 45 | .Timer(TimeSpan.FromMilliseconds(10), TimeSpan.FromMilliseconds(100), Scheduler.Default) 46 | .Take(5) 47 | .Select(_ => new MessageA()); 48 | } 49 | } 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /MemBus.Tests/Rx/When_MemBus_is_used_as_Observable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using System.Text; 4 | using MemBus.Configurators; 5 | using MemBus.Tests.Help; 6 | using System.Reactive.Linq; 7 | 8 | using Xunit; 9 | 10 | namespace MemBus.Tests.Rx 11 | { 12 | 13 | public class WhenMemBusIsUsedAsObservable 14 | { 15 | readonly IBus _bus = BusSetup.StartWith().Construct(); 16 | 17 | [Fact] 18 | public void Apply_extension_method_where() 19 | { 20 | var msgCount = 0; 21 | 22 | var messages = 23 | from msg in _bus.Observe() 24 | where msg.Name == "A" 25 | select msg; 26 | 27 | using (messages.Subscribe(msg => msgCount++)) 28 | { 29 | _bus.Publish(new MessageA {Name = "A"}); 30 | _bus.Publish(new MessageA {Name = "B"}); 31 | _bus.Publish(new MessageA {Name = "A"}); 32 | msgCount.ShouldBeEqualTo(2); 33 | } 34 | } 35 | 36 | [Fact] 37 | public void Apply_extension_method_distinct() 38 | { 39 | var msgs = _bus.Observe().DistinctUntilChanged(m => m.Name); 40 | var sb = new StringBuilder(); 41 | 42 | using (msgs.Subscribe(msg => sb.Append(msg.Name))) 43 | { 44 | _bus.Publish(new MessageA { Name = "A" }); 45 | _bus.Publish(new MessageA { Name = "A" }); 46 | _bus.Publish(new MessageA { Name = "B" }); 47 | _bus.Publish(new MessageA { Name = "B" }); 48 | _bus.Publish(new MessageA { Name = "A" }); 49 | _bus.Publish(new MessageA { Name = "A" }); 50 | sb.ToString().ShouldBeEqualTo("ABA"); 51 | } 52 | } 53 | 54 | [Fact] 55 | public void Class_can_work_as_specialized_observable() 56 | { 57 | var messages = new RxBasedFooObservable(_bus); 58 | int msgCount = 0; 59 | using (messages.Subscribe(msg => msgCount++)) 60 | { 61 | _bus.Publish(new MessageA { Name = "Foo" }); 62 | _bus.Publish(new MessageA { Name = "Bar" }); 63 | _bus.Publish(new MessageA { Name = "Foo" }); 64 | } 65 | msgCount.ShouldBeEqualTo(2); 66 | } 67 | 68 | [Fact] 69 | public void Single_observable_supports_multiple_subscriptions() 70 | { 71 | var sb1 = new StringBuilder(); 72 | var sb2 = new StringBuilder(); 73 | var o = _bus.Observe(); 74 | var d1 = o.Subscribe(msg => sb1.Append("A")); 75 | var d2 = o.Subscribe(msg => sb2.Append("B")); 76 | _bus.Publish(new MessageA()); 77 | sb1.ToString().ShouldBeEqualTo("A"); 78 | sb2.ToString().ShouldBeEqualTo("B"); 79 | d1.Dispose(); 80 | _bus.Publish(new MessageA()); 81 | sb1.ToString().ShouldBeEqualTo("A"); 82 | sb2.ToString().ShouldBeEqualTo("BB"); 83 | d2.Dispose(); 84 | _bus.Publish(new MessageA()); 85 | sb1.ToString().ShouldBeEqualTo("A"); 86 | sb2.ToString().ShouldBeEqualTo("BB"); 87 | } 88 | 89 | 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /MemBus.Tests/Subscribing/FSA_Consuming_Observables.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reactive.Linq; 5 | using JetBrains.Annotations; 6 | using MemBus.Subscribing; 7 | using MemBus.Tests.Help; 8 | using Xunit; 9 | 10 | namespace MemBus.Tests.Subscribing 11 | { 12 | public class FsaConsumingObservables : FlexibleSubscribingIntegrationContext 13 | { 14 | private readonly Tester _tester = new Tester(); 15 | 16 | private class Tester : MessageReceiver 17 | { 18 | [UsedImplicitly] 19 | public void EOne(IObservable messages) 20 | { 21 | messages.Subscribe(Add); 22 | } 23 | } 24 | 25 | protected override IEnumerable GetEndpoints() 26 | { 27 | yield return _tester; 28 | } 29 | 30 | protected override void ConfigureAdapter(FlexibleSubscribeAdapter adp) 31 | { 32 | adp.RegisterMethods(mi => mi.Name.StartsWith("E")); 33 | } 34 | 35 | [Fact] 36 | public void string_msg_received() 37 | { 38 | Bus.Publish("Hello"); 39 | 40 | _tester.AssertContainsMessageOfType(); 41 | } 42 | } 43 | 44 | 45 | public class FsaProducingObservables : FlexibleSubscribingIntegrationContext 46 | { 47 | private readonly Tester _tester = new Tester(); 48 | 49 | private class Tester 50 | { 51 | [UsedImplicitly] 52 | public IObservable EOne() 53 | { 54 | return Observable.Return("Hello from EOne"); 55 | } 56 | } 57 | 58 | protected override IEnumerable GetEndpoints() 59 | { 60 | yield return _tester; 61 | } 62 | 63 | protected override void ConfigureAdapter(FlexibleSubscribeAdapter adp) 64 | { 65 | adp.RegisterMethods(mi => mi.Name.StartsWith("E")); 66 | } 67 | 68 | [Fact] 69 | public void string_msg_received() 70 | { 71 | Messages.OfType().ShouldContain(o => o == "Hello from EOne"); 72 | } 73 | } 74 | 75 | 76 | public class FsaMappingObservables : FlexibleSubscribingIntegrationContext 77 | { 78 | private readonly Tester _tester = new Tester(); 79 | 80 | private class Tester 81 | { 82 | [UsedImplicitly] 83 | public IObservable EOne(IObservable aMessages) 84 | { 85 | return aMessages.Select(a=> new MessageB {Id = a.Name}); 86 | } 87 | } 88 | 89 | protected override IEnumerable GetEndpoints() 90 | { 91 | yield return _tester; 92 | } 93 | 94 | protected override void ConfigureAdapter(FlexibleSubscribeAdapter adp) 95 | { 96 | adp.RegisterMethods(mi => mi.Name.StartsWith("E")); 97 | } 98 | 99 | [Fact] 100 | public void observable_mapping_working() 101 | { 102 | Bus.Publish(new MessageA {Name = "My sweet message"}); 103 | var msgB = Messages.OfType().FirstOrDefault(); 104 | msgB.ShouldNotBeNull(); 105 | msgB.Id.ShouldBeEqualTo("My sweet message"); 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /MemBus.Tests/Subscribing/FSA_Reacting_To_Messages_And_Sending.cs: -------------------------------------------------------------------------------- 1 | using MemBus.Subscribing; 2 | using MemBus.Tests.Help; 3 | using Xunit; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | 8 | namespace MemBus.Tests.Subscribing 9 | { 10 | public class FSA_Reacting_To_Messages_And_Sending : FlexibleSubscribingIntegrationContext, IDisposable 11 | { 12 | private readonly Tester _tester = new Tester(); 13 | 14 | private class Tester : MessageReceiver 15 | { 16 | public void EOne(string message) 17 | { 18 | Messages.Add(message); 19 | } 20 | 21 | public MessageB ETwo(MessageA message) 22 | { 23 | Add(message); 24 | return new MessageB(); 25 | } 26 | 27 | public IEnumerable EEnumerate(MessageA message) 28 | { 29 | Add(message); 30 | yield return new MessageC(); 31 | yield return new MessageC(); 32 | } 33 | 34 | public void Three(MessageB message) 35 | { 36 | Messages.Add(message); 37 | } 38 | } 39 | 40 | protected override IEnumerable GetEndpoints() 41 | { 42 | yield return _tester; 43 | } 44 | 45 | protected override void ConfigureAdapter(FlexibleSubscribeAdapter adp) 46 | { 47 | adp.RegisterMethods(mi => mi.Name.StartsWith("E")); 48 | } 49 | 50 | [Fact] 51 | public void string_msg_received() 52 | { 53 | Bus.Publish("Hello"); 54 | 55 | _tester.AssertContainsMessageOfType(); 56 | } 57 | 58 | [Fact] 59 | public void msg_a_reception_triggers_sending_msg_b() 60 | { 61 | Bus.Publish(new MessageA()); 62 | 63 | _tester.AssertContainsMessageOfType(2); 64 | Messages.OfType().Count().ShouldBeEqualTo(1); 65 | } 66 | 67 | [Fact] 68 | public void msg_a_reception_triggers_sending_c_messages() 69 | { 70 | Bus.Publish(new MessageA()); 71 | Messages.OfType().Count().ShouldBeEqualTo(2); 72 | } 73 | 74 | public void Dispose() 75 | { 76 | Clear(); 77 | _tester.Clear(); 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /MemBus.Tests/Subscribing/FSA_Using_Interface_Based_Subscribing.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using MemBus.Subscribing; 3 | using MemBus.Tests.Help; 4 | using Xunit; 5 | 6 | namespace MemBus.Tests.Subscribing 7 | { 8 | public class FSA_Using_Interface_Based_Subscribing : FlexibleSubscribingIntegrationContext 9 | { 10 | private readonly SomeHandler _handler = new SomeHandler(); 11 | 12 | protected override void ConfigureAdapter(FlexibleSubscribeAdapter adp) 13 | { 14 | adp.ByInterface(typeof(IWeirdHandler<>)); 15 | } 16 | 17 | protected override IEnumerable GetEndpoints() 18 | { 19 | yield return _handler; 20 | } 21 | 22 | [Fact] 23 | public void Msg_A_was_received_as_produced_by_observable() 24 | { 25 | _handler.MsgACalls.ShouldBeEqualTo(1); 26 | } 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /MemBus.Tests/Subscribing/FSA_Using_Predicate_Based_Subscribing.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using MemBus.Subscribing; 3 | using MemBus.Tests.Help; 4 | using Xunit; 5 | 6 | namespace MemBus.Tests.Subscribing 7 | { 8 | 9 | 10 | public class FSA_Using_Predicate_Based_Subscribing : FlexibleSubscribingIntegrationContext 11 | { 12 | private readonly SomeHandler _handler = new SomeHandler(); 13 | 14 | protected override void ConfigureAdapter(FlexibleSubscribeAdapter adp) 15 | { 16 | adp.RegisterMethods("Handle").RegisterMethods(mi => true); 17 | } 18 | 19 | protected override IEnumerable GetEndpoints() 20 | { 21 | yield return _handler; 22 | } 23 | 24 | protected override void AdditionalSetup() 25 | { 26 | Bus.Publish(new MessageA()); 27 | Bus.Publish(new MessageB()); 28 | Bus.Publish(new MessageC()); 29 | } 30 | 31 | [Fact] 32 | public void Handle_was_found() 33 | { 34 | _handler.MsgACalls.ShouldBeEqualTo(1); 35 | } 36 | 37 | [Fact] 38 | public void Route_was_found() 39 | { 40 | _handler.MsgBCalls.ShouldBeEqualTo(1); 41 | } 42 | 43 | [Fact] 44 | public void Any_method_from_the_route_and_explicit() 45 | { 46 | _handler.MsgCCalls.ShouldBeEqualTo(2); 47 | } 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /MemBus.Tests/Subscribing/FlexibleSubscribingIntegrationContext.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using MemBus.Configurators; 4 | using MemBus.Subscribing; 5 | using MemBus.Tests.Help; 6 | 7 | namespace MemBus.Tests.Subscribing 8 | { 9 | public class MessageReceiver 10 | { 11 | public readonly List Messages = new List(); 12 | 13 | public void Clear() 14 | { 15 | Messages.Clear(); 16 | } 17 | 18 | public void Add(object msg) 19 | { 20 | Messages.Add(msg); 21 | } 22 | 23 | public void AssertContainsMessageOfType(int count = 1) 24 | { 25 | Messages.OfType().ShouldHaveCount(count); 26 | } 27 | } 28 | 29 | public class FlexibleSubscribingIntegrationContext 30 | { 31 | protected readonly IBus Bus; 32 | protected readonly List Messages = new List(); 33 | 34 | protected FlexibleSubscribingIntegrationContext() 35 | { 36 | Bus = BusSetup 37 | .StartWith() 38 | .Apply(ConfigureAdapter) 39 | .Construct(); 40 | Bus.Subscribe(MessageCapturing); 41 | // ReSharper disable VirtualMemberCallInConstructor 42 | foreach (var endpoints in GetEndpoints()) 43 | { 44 | Bus.Subscribe(endpoints); 45 | } 46 | 47 | AdditionalSetup(); 48 | // ReSharper restore VirtualMemberCallInConstructor 49 | } 50 | 51 | protected virtual void AdditionalSetup() 52 | { 53 | } 54 | 55 | protected virtual IEnumerable GetEndpoints() 56 | { 57 | yield break; 58 | } 59 | 60 | private void MessageCapturing(object message) 61 | { 62 | Messages.Add(message); 63 | } 64 | 65 | protected virtual void ConfigureAdapter(FlexibleSubscribeAdapter adp) 66 | { 67 | } 68 | 69 | protected void Clear() 70 | { 71 | Messages.Clear(); 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /MemBus.Tests/Subscribing/Using_Composite_Subscription.cs: -------------------------------------------------------------------------------- 1 | using MemBus.Tests.Help; 2 | using System.Linq; 3 | using Xunit; 4 | 5 | namespace MemBus.Tests.Subscribing 6 | { 7 | 8 | public class Using_Composite_Subscription 9 | { 10 | [Fact] 11 | public void empty_composite_would_handle_any_message() 12 | { 13 | var c = new CompositeSubscription(); 14 | (c as ISubscription).Handles(typeof(MessageA)).ShouldBeTrue(); 15 | } 16 | 17 | [Fact] 18 | public void push_message_on_empty_composite_is_legal() 19 | { 20 | var c = new CompositeSubscription(); 21 | (c as ISubscription).Push(new MessageA()); 22 | } 23 | 24 | [Fact] 25 | public void adding_disparate_handlers_is_not_validated_by_composite() 26 | { 27 | var c = new CompositeSubscription {new MockSubscription(), new MockSubscription()}; 28 | c.ShouldHaveCount(2); 29 | } 30 | 31 | [Fact] 32 | public void composite_enumerator_works() 33 | { 34 | var c = new CompositeSubscription { new MockSubscription(), new MockSubscription() }; 35 | c.ShouldHaveCount(2); 36 | } 37 | 38 | [Fact] 39 | public void push_delegates_to_all_childs() 40 | { 41 | var c = new CompositeSubscription {new MockSubscription(), new MockSubscription()}; 42 | c.Push(new MessageA()); 43 | c.OfType>().All(m=>m.Received == 1).ShouldBeTrue(); 44 | } 45 | 46 | [Fact] 47 | public void dispose_of_subscription_removes_it_from_composite() 48 | { 49 | var subscription = new MockSubscription(); 50 | var c = new CompositeSubscription { subscription }; 51 | c.ShouldHaveCount(1); 52 | subscription.Dispose(); 53 | c.ShouldHaveCount(0); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /MemBus.Tests/Subscribing/Using_disposable_method_subscription.cs: -------------------------------------------------------------------------------- 1 | using MemBus.Subscribing; 2 | using MemBus.Tests.Help; 3 | using System.Linq; 4 | 5 | using Xunit; 6 | 7 | 8 | 9 | namespace MemBus.Tests.Subscribing 10 | { 11 | 12 | public class Using_Disposable_Method_Subscription 13 | { 14 | [Fact] 15 | public void the_basic_method_subscription_is_contravariant() 16 | { 17 | var sub = new MethodInvocation(msg => { }); 18 | sub.Handles(typeof(MessageASpecialization)).ShouldBeTrue(); 19 | } 20 | 21 | 22 | [Fact] 23 | public void Handle_type_derived_from_action() 24 | { 25 | int callCount = 0; 26 | var sub = new DisposableSubscription(new MethodInvocation(msg => callCount++)); 27 | sub.Handles(typeof(MessageA)).ShouldBeTrue(); 28 | } 29 | 30 | [Fact] 31 | public void passed_in_action_is_called() 32 | { 33 | int callCount = 0; 34 | var sub = new DisposableSubscription(new MethodInvocation(msg => callCount++)); 35 | sub.Push(new MessageA()); 36 | callCount.ShouldBeEqualTo(1); 37 | } 38 | 39 | [Fact] 40 | public void calling_disposer_raises_disposed_evt() 41 | { 42 | var disposeCalled = false; 43 | IDisposableSubscription sub = new DisposableSubscription(null); 44 | sub.Disposed += (s, e) => disposeCalled = true; 45 | sub.GetDisposer().Dispose(); 46 | disposeCalled.ShouldBeTrue(); 47 | } 48 | 49 | 50 | [Fact] 51 | public void Adapter_subscriptions_can_also_be_disposed() 52 | { 53 | var b = new MethodScanner("Handle").MakeBuilder(); 54 | var disposableSub = new DisposableSubscription(b.BuildSubscriptions(new SomeHandler()).First()); 55 | ISubscriptionResolver resolver = new CompositeSubscription(); 56 | resolver.Add(disposableSub); 57 | 58 | var subs = resolver.GetSubscriptionsFor(new MessageA()); 59 | subs.ShouldHaveCount(1); 60 | 61 | disposableSub.GetDisposer().Dispose(); 62 | 63 | subs = resolver.GetSubscriptionsFor(new MessageA()); 64 | subs.ShouldHaveCount(0); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /MemBus.Tests/Subscribing/Using_subscription_shapes.cs: -------------------------------------------------------------------------------- 1 | using MemBus.Subscribing; 2 | using MemBus.Tests.Help; 3 | using Xunit; 4 | 5 | namespace MemBus.Tests.Subscribing 6 | { 7 | 8 | public class Using_subscription_shapes 9 | { 10 | [Fact] 11 | public void Correct_sequence_of_matroschka() 12 | { 13 | var m = new SubscriptionShaperAggregate {new TestShaper("A"), new TestShaper("B")}; 14 | var s = (NamedSubscription)m.EnhanceSubscription(new NamedSubscription("First", null)); 15 | s.Name.ShouldBeEqualTo("B"); 16 | ((NamedSubscription)s.Inner).Name.ShouldBeEqualTo("A"); 17 | } 18 | 19 | [Fact] 20 | public void Next_to_inner_produces_correct_sequence() 21 | { 22 | var m = new SubscriptionShaperAggregate { new TestShaper("A") }; 23 | m.AddNextToInner(new TestShaper("B")); 24 | var s = (NamedSubscription)m.EnhanceSubscription(new NamedSubscription("First", null)); 25 | s.Name.ShouldBeEqualTo("A"); 26 | ((NamedSubscription)s.Inner).Name.ShouldBeEqualTo("B"); 27 | } 28 | 29 | [Fact] 30 | public void Denial_of_shape_correctly_propagates() 31 | { 32 | var s = 33 | new DisposableSubscription( 34 | new FilteredSubscription(m => true, 35 | new UiDispatchingSubscription(null, 36 | new DenyingSubscription()))); 37 | s.Deny.ShouldBeTrue(); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /MemBus.Tests/Subscribing/When_Resolving_Subscriptions.cs: -------------------------------------------------------------------------------- 1 | using MemBus.Tests.Help; 2 | using System.Linq; 3 | using Xunit; 4 | 5 | 6 | namespace MemBus.Tests.Subscribing 7 | { 8 | public class When_Resolving_Subscriptions 9 | { 10 | private CompositeResolver _resolver; 11 | 12 | public When_Resolving_Subscriptions() 13 | { 14 | _resolver = new CompositeResolver(new SimpleResolver {new MockSubscription()}, 15 | new SimpleResolver { new MockSubscription(), new MockSubscription()}); 16 | } 17 | 18 | [Fact] 19 | public void returns_single_subscription_for_msg_b() 20 | { 21 | var subs = _resolver.GetSubscriptionsFor(new MessageB()); 22 | subs.ShouldHaveCount(1); 23 | subs.Single().Handles(typeof(MessageB)).ShouldBeTrue(); 24 | } 25 | 26 | [Fact] 27 | public void returns_none_for_msg_c() 28 | { 29 | var subs = _resolver.GetSubscriptionsFor(new MessageC()); 30 | subs.ShouldHaveCount(0); 31 | } 32 | 33 | [Fact] 34 | public void returns_both_msg_a_subscriptions() 35 | { 36 | var subs = _resolver.GetSubscriptionsFor(new MessageA()); 37 | subs.ShouldHaveCount(2); 38 | } 39 | 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /MemBus.Tests/Subscribing/When_subscribing.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using MemBus.Configurators; 4 | using MemBus.Setup; 5 | using MemBus.Subscribing; 6 | using MemBus.Tests.Help; 7 | using Xunit; 8 | 9 | namespace MemBus.Tests.Subscribing 10 | { 11 | public class WhenSubscribing 12 | { 13 | [Fact] 14 | public void Conventions_allow_changing_the_shape() 15 | { 16 | var sb = new StringBuilder(); 17 | var bus = BusSetup.StartWith(new BusSetupWithTestShapers(sb)).Construct(); 18 | 19 | using (bus.Subscribe(msg => { })) 20 | bus.Publish(new MessageB()); 21 | 22 | sb.ToString().ShouldBeEqualTo("AB"); 23 | } 24 | 25 | [Fact] 26 | public void The_default_is_applied_when_no_specials_apply() 27 | { 28 | var sb = new StringBuilder(); 29 | var bus = BusSetup.StartWith(new BusSetupWithDefaultShape(sb)).Construct(); 30 | 31 | using (bus.Subscribe(msg => { })) 32 | bus.Publish(new MessageA()); 33 | 34 | sb.ToString().ShouldBeEqualTo("Bar"); 35 | } 36 | 37 | [Fact] 38 | public void A_shape_gets_access_to_services() 39 | { 40 | var testShaper = new TestShaper("Test"); 41 | BusSetup.StartWith(new BusSetupPutShapeOnMsgA(testShaper)).Construct(); 42 | testShaper.Services.ShouldNotBeNull(); 43 | } 44 | 45 | [Fact] 46 | public void The_instance_of_a_static_method_is_null() 47 | { 48 | var sub = new MethodInvocation(Substatic); 49 | ((IKnowsSubscribedInstance) sub).Instance.ShouldBeNull(); 50 | } 51 | 52 | [Fact] 53 | public void Meth_invocation_implements_knows_instance() 54 | { 55 | var sub = new MethodInvocation(Sub); 56 | ((IKnowsSubscribedInstance) sub).Instance.ShouldBeOfType(); 57 | } 58 | 59 | [Fact] 60 | public void Using_shape_overload_directly_works() 61 | { 62 | var b = BusSetup.StartWith().Construct(); 63 | var counter = 0; 64 | using (var d = b.Subscribe((string s) => counter++, new ShapeToFilter(s => s == "A"))) 65 | { 66 | d.ShouldNotBeNull(); 67 | b.Publish("A"); 68 | b.Publish("B"); 69 | } 70 | 71 | counter.ShouldBeEqualTo(1); 72 | } 73 | 74 | [Fact] 75 | // Test for https://github.com/flq/MemBus/issues/17 76 | public void Large_number_of_subscribers() 77 | { 78 | var b = BusSetup.StartWith().Construct(); 79 | 80 | for (var i = 0; i < 100000; i++) 81 | { 82 | b.Subscribe((int x) => Console.WriteLine(x)); 83 | } 84 | } 85 | 86 | #pragma warning disable xUnit1013 87 | public void Sub(object msg) 88 | { 89 | } 90 | 91 | public static void Substatic(object msg) 92 | { 93 | } 94 | #pragma warning restore xUnit1013 95 | } 96 | 97 | public class BusSetupWithTestShapers : ISetup 98 | { 99 | private readonly StringBuilder _sb; 100 | 101 | public BusSetupWithTestShapers(StringBuilder sb) 102 | { 103 | _sb = sb; 104 | } 105 | 106 | public void Accept(IConfigurableBus setup) 107 | { 108 | setup.ConfigureSubscribing( 109 | s => s.MessageMatch(mi => mi.IsType(), 110 | sc => sc.ShapeOutwards( 111 | new TestShaper("B", () => _sb.Append("B")), 112 | new TestShaper("A", () => _sb.Append("A")) 113 | ))); 114 | } 115 | } 116 | 117 | public class BusSetupWithDefaultShape : ISetup 118 | { 119 | private readonly StringBuilder _sb; 120 | 121 | public BusSetupWithDefaultShape(StringBuilder sb) 122 | { 123 | _sb = sb; 124 | } 125 | 126 | public void Accept(IConfigurableBus setup) 127 | { 128 | setup.ConfigureSubscribing( 129 | s => 130 | { 131 | s.DefaultShapeOutwards(new TestShaper("Bar", () => _sb.Append("Bar"))); 132 | s.MessageMatch(mi => mi.IsType(), 133 | sc => sc.ShapeOutwards(new TestShaper("Foo", () => _sb.Append("Foo")))); 134 | }); 135 | } 136 | } 137 | 138 | public class BusSetupPutShapeOnMsgA : ISetup 139 | { 140 | private readonly TestShaper _shaper; 141 | 142 | 143 | public BusSetupPutShapeOnMsgA(TestShaper shaper) 144 | { 145 | _shaper = shaper; 146 | } 147 | 148 | public void Accept(IConfigurableBus setup) 149 | { 150 | setup.AddService(new StringBuilder()); 151 | setup.ConfigureSubscribing( 152 | s => s.MessageMatch(mi => mi.IsType(), c => c.ShapeOutwards(_shaper))); 153 | } 154 | } 155 | } -------------------------------------------------------------------------------- /MemBus.Tests/When_using_standard_resolver.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using MemBus.Subscribing; 3 | using MemBus.Tests.Help; 4 | using Xunit; 5 | 6 | namespace MemBus.Tests 7 | { 8 | 9 | public class ResolverTestContext 10 | { 11 | protected virtual ISubscriptionResolver GetResolver() 12 | { 13 | return new CompositeSubscription(); 14 | } 15 | 16 | [Fact] 17 | public void Caching_resolver_accepts_and_returns_subscriptions() 18 | { 19 | var sub = Helpers.MockSubscriptionThatHandles(); 20 | var r = (ISubscriptionResolver)new CompositeSubscription(); 21 | r.Add(sub); 22 | var subs = r.GetSubscriptionsFor(new MessageA()).ToList(); 23 | subs.ShouldHaveCount(1); 24 | } 25 | 26 | [Fact] 27 | public void Sequence_of_addition_and_disposal_works_as_shown() 28 | { 29 | var sub1 = new MockSubscription(); 30 | var sub2 = new MockSubscription(); 31 | var sub3 = new MockSubscription(); 32 | 33 | var r = GetResolver(); 34 | r.Add(sub1); 35 | r.GetSubscriptionsFor(new MessageA()).ShouldHaveCount(1); 36 | r.Add(sub2); 37 | r.GetSubscriptionsFor(new MessageA()).ShouldHaveCount(2); 38 | r.GetSubscriptionsFor(new MessageA()).ShouldHaveCount(2); 39 | r.Add(sub3); 40 | r.GetSubscriptionsFor(new MessageB()).ShouldHaveCount(1); 41 | sub1.Dispose(); 42 | r.GetSubscriptionsFor(new MessageA()).ShouldHaveCount(1); 43 | sub3.Dispose(); 44 | r.GetSubscriptionsFor(new MessageB()).ShouldHaveCount(0); 45 | 46 | } 47 | 48 | [Fact] 49 | public void correct_behavior_of_resolver_with_regard_to_subscribe_dispose_subscribe_publish_twice() 50 | { 51 | var sub1 = new MockSubscription(); 52 | var r = GetResolver(); 53 | 54 | r.Add(sub1); 55 | r.GetSubscriptionsFor(new MessageA()).ShouldHaveCount(1); 56 | sub1.Dispose(); 57 | 58 | sub1 = new MockSubscription(); 59 | r.Add(sub1); 60 | r.GetSubscriptionsFor(new MessageB()).ShouldHaveCount(0); 61 | r.GetSubscriptionsFor(new MessageA()).ShouldHaveCount(1); 62 | } 63 | 64 | [Fact] 65 | public void correct_behavior_subscribe_get_subscribe_new_then_get_old() 66 | { 67 | var sub1 = new MockSubscription(); 68 | var sub2 = new MockSubscription(); 69 | var r = GetResolver(); 70 | 71 | r.Add(sub1); 72 | r.GetSubscriptionsFor(new MessageA()).ShouldHaveCount(1); 73 | r.Add(sub2); 74 | r.GetSubscriptionsFor(new MessageA()).ShouldHaveCount(1); 75 | } 76 | 77 | [Fact] 78 | public void correct_behavior_regarding_contravariance() 79 | { 80 | var sub = new MethodInvocation(f => {}); 81 | var r = GetResolver(); 82 | r.Add(sub); 83 | r.GetSubscriptionsFor(new Clong()).ShouldHaveCount(1); 84 | r.GetSubscriptionsFor(new Clung()).ShouldHaveCount(1); 85 | } 86 | 87 | [Fact] 88 | public void correct_behavior_not_getting_message_twice() 89 | { 90 | var sub = new MethodInvocation(f => { }); 91 | var r = GetResolver(); 92 | r.Add(sub); 93 | r.GetSubscriptionsFor(new Clong()).ShouldHaveCount(1); 94 | r.GetSubscriptionsFor(new Clong()).ShouldHaveCount(1); 95 | } 96 | } 97 | 98 | public class Clong {} 99 | public class Clung : Clong {} 100 | 101 | } -------------------------------------------------------------------------------- /MemBus.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /MemBus.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26228.4 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MemBus", "MemBus\MemBus.csproj", "{CF9CB06E-1BF1-4A34-B393-0CE21DCDA271}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MemBus.Tests", "MemBus.Tests\MemBus.Tests.csproj", "{D6F469D9-0709-405E-BC87-44B8E8A42502}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|x64 = Debug|x64 14 | Debug|x86 = Debug|x86 15 | Release|Any CPU = Release|Any CPU 16 | Release|x64 = Release|x64 17 | Release|x86 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {CF9CB06E-1BF1-4A34-B393-0CE21DCDA271}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {CF9CB06E-1BF1-4A34-B393-0CE21DCDA271}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {CF9CB06E-1BF1-4A34-B393-0CE21DCDA271}.Debug|x64.ActiveCfg = Debug|Any CPU 23 | {CF9CB06E-1BF1-4A34-B393-0CE21DCDA271}.Debug|x64.Build.0 = Debug|Any CPU 24 | {CF9CB06E-1BF1-4A34-B393-0CE21DCDA271}.Debug|x86.ActiveCfg = Debug|Any CPU 25 | {CF9CB06E-1BF1-4A34-B393-0CE21DCDA271}.Debug|x86.Build.0 = Debug|Any CPU 26 | {CF9CB06E-1BF1-4A34-B393-0CE21DCDA271}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {CF9CB06E-1BF1-4A34-B393-0CE21DCDA271}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {CF9CB06E-1BF1-4A34-B393-0CE21DCDA271}.Release|x64.ActiveCfg = Release|Any CPU 29 | {CF9CB06E-1BF1-4A34-B393-0CE21DCDA271}.Release|x64.Build.0 = Release|Any CPU 30 | {CF9CB06E-1BF1-4A34-B393-0CE21DCDA271}.Release|x86.ActiveCfg = Release|Any CPU 31 | {CF9CB06E-1BF1-4A34-B393-0CE21DCDA271}.Release|x86.Build.0 = Release|Any CPU 32 | {D6F469D9-0709-405E-BC87-44B8E8A42502}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {D6F469D9-0709-405E-BC87-44B8E8A42502}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {D6F469D9-0709-405E-BC87-44B8E8A42502}.Debug|x64.ActiveCfg = Debug|Any CPU 35 | {D6F469D9-0709-405E-BC87-44B8E8A42502}.Debug|x64.Build.0 = Debug|Any CPU 36 | {D6F469D9-0709-405E-BC87-44B8E8A42502}.Debug|x86.ActiveCfg = Debug|Any CPU 37 | {D6F469D9-0709-405E-BC87-44B8E8A42502}.Debug|x86.Build.0 = Debug|Any CPU 38 | {D6F469D9-0709-405E-BC87-44B8E8A42502}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {D6F469D9-0709-405E-BC87-44B8E8A42502}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {D6F469D9-0709-405E-BC87-44B8E8A42502}.Release|x64.ActiveCfg = Release|Any CPU 41 | {D6F469D9-0709-405E-BC87-44B8E8A42502}.Release|x64.Build.0 = Release|Any CPU 42 | {D6F469D9-0709-405E-BC87-44B8E8A42502}.Release|x86.ActiveCfg = Release|Any CPU 43 | {D6F469D9-0709-405E-BC87-44B8E8A42502}.Release|x86.Build.0 = Release|Any CPU 44 | EndGlobalSection 45 | GlobalSection(SolutionProperties) = preSolution 46 | HideSolutionNode = FALSE 47 | EndGlobalSection 48 | EndGlobal 49 | -------------------------------------------------------------------------------- /MemBus/Bus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using MemBus.Publishing; 4 | using MemBus.Setup; 5 | using MemBus.Subscribing; 6 | using MemBus.Support; 7 | 8 | namespace MemBus 9 | { 10 | internal class Bus : IConfigurableBus, IBus 11 | { 12 | private readonly PublishChainCasing _publishChainCasing; 13 | private readonly Subscriber _subscriber; 14 | private readonly IServices _services = new StandardServices(); 15 | 16 | private readonly DisposeContainer _disposer; 17 | 18 | private volatile bool _isDisposed; 19 | private bool _isDisposing; 20 | 21 | internal Bus() 22 | { 23 | _services.Add(this); 24 | _services.Add(new ObservableRelay(this)); 25 | _publishChainCasing = new PublishChainCasing(this); 26 | _subscriber = new Subscriber(_services); 27 | _disposer = new DisposeContainer { _publishChainCasing, _subscriber, (IDisposable)_services }; 28 | } 29 | 30 | void IConfigurableBus.ConfigurePublishing(Action configurePipeline) 31 | { 32 | CheckDisposed(); 33 | configurePipeline(_publishChainCasing); 34 | } 35 | 36 | public void ConfigureSubscribing(Action configure) 37 | { 38 | ((IConfigurableSubscriber)_subscriber).ConfigureSubscribing(configure); 39 | } 40 | 41 | void IConfigurableSubscriber.AddResolver(ISubscriptionResolver resolver) 42 | { 43 | ((IConfigurableSubscriber)_subscriber).AddResolver(resolver); 44 | } 45 | 46 | void IConfigurableSubscriber.AddSubscription(ISubscription subscription) 47 | { 48 | ((IConfigurableSubscriber)_subscriber).AddSubscription(subscription); 49 | } 50 | 51 | void IConfigurableBus.AddService(T service) 52 | { 53 | CheckDisposed(); 54 | _services.Add(service); 55 | } 56 | 57 | public void Publish(object message) 58 | { 59 | CheckDisposed(); 60 | var subs = _subscriber.GetSubscriptionsFor(message); 61 | var t = new PublishToken(message, subs); 62 | _publishChainCasing.LookAt(t); 63 | } 64 | 65 | public async Task PublishAsync(object message) 66 | { 67 | CheckDisposed(); 68 | var subs = _subscriber.GetSubscriptionsFor(message); 69 | var t = new PublishToken(message, subs); 70 | await _publishChainCasing.LookAtAsync(t); 71 | } 72 | 73 | public IDisposable Publish(IObservable observable) 74 | { 75 | CheckDisposed(); 76 | return _services.Get().Connect(observable); 77 | } 78 | 79 | public IDisposable Subscribe(Action subscription) 80 | { 81 | return _subscriber.Subscribe(subscription); 82 | } 83 | 84 | public IDisposable Subscribe(object subscriber) 85 | { 86 | return _subscriber.Subscribe(subscriber); 87 | } 88 | 89 | public IDisposable Subscribe(Action subscription, ISubscriptionShaper customization) 90 | { 91 | return _subscriber.Subscribe(subscription, customization); 92 | } 93 | 94 | public IObservable Observe() 95 | { 96 | return _subscriber.Observe(); 97 | } 98 | 99 | public void Dispose() 100 | { 101 | if (_isDisposing) 102 | return; 103 | 104 | try 105 | { 106 | _isDisposing = true; 107 | _disposer.Dispose(); 108 | 109 | } 110 | finally 111 | { 112 | _isDisposing = false; 113 | } 114 | _isDisposed = true; 115 | } 116 | 117 | internal IServices Services { get { return _services; }} 118 | 119 | private void CheckDisposed() 120 | { 121 | if (_isDisposed) 122 | throw new ObjectDisposedException("Bus"); 123 | } 124 | } 125 | } -------------------------------------------------------------------------------- /MemBus/CompositeResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace MemBus 6 | { 7 | internal class CompositeResolver : ISubscriptionResolver, IDisposable 8 | { 9 | readonly List _resolvers = new List(); 10 | 11 | public CompositeResolver(params ISubscriptionResolver[] resolvers) 12 | { 13 | _resolvers.AddRange(resolvers); 14 | } 15 | 16 | public IEnumerable GetSubscriptionsFor(object message) 17 | { 18 | return _resolvers.SelectMany(r => r.GetSubscriptionsFor(message)); 19 | } 20 | 21 | public void Add(ISubscriptionResolver resolver) 22 | { 23 | _resolvers.Add(resolver); 24 | } 25 | 26 | public bool Add(ISubscription subscription) 27 | { 28 | var wasAdded = false; 29 | foreach (var resolver in _resolvers) 30 | { 31 | wasAdded = resolver.Add(subscription); 32 | if (wasAdded) break; 33 | } 34 | return wasAdded; 35 | } 36 | 37 | public void Dispose() 38 | { 39 | _resolvers.Clear(); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /MemBus/CompositeSubscription.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Threading; 5 | using MemBus.Subscribing; 6 | using MemBus.Support; 7 | using System.Linq; 8 | 9 | namespace MemBus 10 | { 11 | /// 12 | /// The composite subscription wraps a number of subscriptions and treats them as one. 13 | /// This class is not associated with any message type and will accept any kind of class implementing 14 | /// . This class also acts as . 15 | /// 16 | public class CompositeSubscription : ISubscription, IEnumerable, ISubscriptionResolver 17 | { 18 | private readonly HashSet _subscriptions = new HashSet(); 19 | private readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim(); 20 | 21 | public CompositeSubscription() { } 22 | 23 | public CompositeSubscription(IEnumerable subscriptions) 24 | { 25 | AddRange(subscriptions); 26 | } 27 | 28 | /// 29 | /// returns true if no subscriptions are contained 30 | /// 31 | public bool IsEmpty => _subscriptions.Count == 0; 32 | 33 | /// 34 | /// Pushes the message to the contained subscriptions. 35 | /// 36 | public void Push(object message) 37 | { 38 | foreach (var s in Snapshot) 39 | s.Push(message); 40 | } 41 | 42 | /// 43 | /// This event is raised if any of the contained subscriptions is Disposed. 44 | /// 45 | public event EventHandler Disposed; 46 | 47 | bool ISubscription.Handles(Type messageType) 48 | { 49 | return Snapshot.All(s => s.Handles(messageType)); 50 | } 51 | 52 | public IEnumerator GetEnumerator() 53 | { 54 | return Snapshot.GetEnumerator(); 55 | } 56 | 57 | IEnumerable ISubscriptionResolver.GetSubscriptionsFor(object message) 58 | { 59 | return Snapshot.Where(s => s.Handles(message.GetType())).ToArray(); 60 | } 61 | 62 | /// 63 | public bool Add(ISubscription subscription) 64 | { 65 | if (subscription == null) 66 | throw new ArgumentNullException("subscription", "Attempt to add a Null Reference to Composite subscription."); 67 | try 68 | { 69 | _rwLock.EnterWriteLock(); 70 | JustAdd(subscription); 71 | return true; 72 | } 73 | finally 74 | { 75 | _rwLock.ExitWriteLock(); 76 | } 77 | } 78 | 79 | 80 | IEnumerator IEnumerable.GetEnumerator() 81 | { 82 | return GetEnumerator(); 83 | } 84 | 85 | private void AddRange(IEnumerable subscriptions) 86 | { 87 | foreach (var s in subscriptions) 88 | JustAdd(s); 89 | } 90 | 91 | private void JustAdd(ISubscription subscription) 92 | { 93 | var disposableSub = GetDisposableSub(subscription); 94 | disposableSub.Disposed += OnSubscriptionDisposed; 95 | _subscriptions.Add(disposableSub); 96 | } 97 | 98 | private void OnSubscriptionDisposed(object sender, EventArgs e) 99 | { 100 | try 101 | { 102 | _rwLock.EnterWriteLock(); 103 | _subscriptions.Remove(sender as IDisposableSubscription); 104 | Disposed.Raise(sender); 105 | } 106 | finally 107 | { 108 | _rwLock.ExitWriteLock(); 109 | } 110 | } 111 | 112 | private static IDisposableSubscription GetDisposableSub(ISubscription subscription) 113 | { 114 | return subscription is IDisposableSubscription ? 115 | (IDisposableSubscription)subscription : new DisposableSubscription(subscription); 116 | } 117 | 118 | private IReadOnlyCollection Snapshot 119 | { 120 | get 121 | { 122 | try 123 | { 124 | _rwLock.EnterReadLock(); 125 | var disposableSubscriptions = _subscriptions.ToArray(); 126 | return disposableSubscriptions; 127 | } 128 | finally 129 | { 130 | _rwLock.ExitReadLock(); 131 | } 132 | } 133 | } 134 | } 135 | } -------------------------------------------------------------------------------- /MemBus/Configurators/AsyncConfiguration.cs: -------------------------------------------------------------------------------- 1 | using MemBus.Messages; 2 | using MemBus.Publishing; 3 | using MemBus.Setup; 4 | using MemBus.Subscribing; 5 | 6 | namespace MemBus.Configurators 7 | { 8 | /// 9 | /// A parallel publisher is used that publishes messages in parallel. With this setup does NOT block. 10 | /// Exceptions will become available once all subscriptions are done processing the message as message. 11 | /// 12 | public class AsyncConfiguration : ISetup 13 | { 14 | 15 | /// 16 | /// 17 | /// 18 | public virtual void Accept(IConfigurableBus setup) 19 | { 20 | setup.ConfigurePublishing(p => p.DefaultPublishPipeline(new ParallelNonBlockingPublisher())); 21 | setup.AddResolver(new CompositeSubscription()); 22 | setup.ConfigureSubscribing(cs => cs.ApplyOnNewSubscription(new ShapeToDispose())); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /MemBus/Configurators/Conservative.cs: -------------------------------------------------------------------------------- 1 | using MemBus.Publishing; 2 | using MemBus.Setup; 3 | using MemBus.Subscribing; 4 | 5 | namespace MemBus.Configurators 6 | { 7 | /// 8 | /// Sets up a SequentialPublisher, and a table based resolver. 9 | /// The message push process is interrupted when an exception is raised by a subscription. 10 | /// 11 | public class Conservative : ISetup 12 | { 13 | void ISetup.Accept(IConfigurableBus setup) 14 | { 15 | setup.ConfigurePublishing(p => p.DefaultPublishPipeline(new SequentialPublisher())); 16 | setup.AddResolver(new CompositeSubscription()); 17 | setup.ConfigureSubscribing(cs => cs.ApplyOnNewSubscription(new ShapeToDispose())); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /MemBus/Configurators/Fast.cs: -------------------------------------------------------------------------------- 1 | using MemBus.Publishing; 2 | using MemBus.Setup; 3 | using MemBus.Subscribing; 4 | 5 | namespace MemBus.Configurators 6 | { 7 | /// 8 | /// A fire and forget publisher is used that publishes messages in parallel. With this setup does NOT block. 9 | /// 10 | public class Fast : ISetup 11 | { 12 | 13 | /// 14 | /// 15 | /// 16 | public virtual void Accept(IConfigurableBus setup) 17 | { 18 | setup.ConfigurePublishing(p => p.DefaultPublishPipeline(new FireAndForgetPublisher())); 19 | setup.AddResolver(new CompositeSubscription()); 20 | setup.ConfigureSubscribing(cs => cs.ApplyOnNewSubscription(new ShapeToDispose())); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /MemBus/Configurators/IoCSupport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using MemBus.Setup; 4 | using MemBus.Support; 5 | 6 | 7 | namespace MemBus.Configurators 8 | { 9 | /// 10 | /// Add ServiceLocator Support 11 | /// 12 | public class IoCSupport : ISetup 13 | { 14 | private IocAdapter _adapter; 15 | private Type _handlerType; 16 | private Func _messageTypeResolver = msgT => msgT; 17 | 18 | /// 19 | /// Add an IoCadapter that will be used to resolve subscriptions. subscriptions will be resolved based on the interface you provide 20 | /// 21 | public IoCSupport SetAdapter(IocAdapter adapter) 22 | { 23 | _adapter = adapter; 24 | return this; 25 | } 26 | 27 | /// 28 | /// Set the interface type. It needs to be a generic interface with one type argument. 29 | /// (Don't specify the type, e.g. typeof(IHandles<>) 30 | /// It must define only one method that is void and accepts one argument 31 | /// (typically of the message type) 32 | /// 33 | public IoCSupport SetHandlerInterface(Type openGenericHandlerType) 34 | { 35 | _handlerType = openGenericHandlerType; 36 | return this; 37 | } 38 | 39 | /// 40 | /// Set the function that will be used to resolve the type of message that will be used when resolving handler type. 41 | /// It may be useful if the generic type of the open generic handler interface is not exactly matching the type of the messages passed. 42 | /// 43 | public IoCSupport SetMessageTypeResolver(Func messageTypeResolver) 44 | { 45 | _messageTypeResolver = messageTypeResolver; 46 | return this; 47 | } 48 | 49 | void ISetup.Accept(IConfigurableBus setup) 50 | { 51 | ThrowIfBadPreconditions(); 52 | setup.AddService(_adapter); 53 | setup.AddResolver(new IoCBasedResolver(_adapter, _handlerType, _messageTypeResolver)); 54 | } 55 | 56 | private void ThrowIfBadPreconditions() 57 | { 58 | if (_adapter == null) 59 | throw new ArgumentException("The IocAdapter has not been specified on the Ioc support."); 60 | if (_handlerType == null) 61 | throw new ArgumentException("No handler type has been specified."); 62 | if (!_handlerType.GetTypeInfo().IsGenericTypeDefinition) 63 | throw new ArgumentException("An open generic should be specified as handler type"); 64 | if (_handlerType.GetTypeInfo().GenericTypeParameters.Length != 1) 65 | throw new ArgumentException("An open generic should be specified that has only one type argument"); 66 | if (!_handlerType.InterfaceIsSuitableAsIoCHandler()) 67 | throw new ArgumentException("Type should contain a single method with one argument and void return"); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /MemBus/Configurators/RichClientFrontend.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using MemBus.Messages; 3 | using MemBus.Publishing; 4 | using MemBus.Setup; 5 | using MemBus.Subscribing; 6 | 7 | namespace MemBus.Configurators 8 | { 9 | /// 10 | /// Setup the Bus with this on the UI thread (Important!). 11 | /// A parallel publisher is used that publishes messages in parallel but blocks until all message processing is done. 12 | /// Exceptions will not stop the publishing and become available on the bus as message. 13 | /// The setup allows you to call when doing a subscription. 14 | /// 15 | public class RichClientFrontend : ISetup 16 | { 17 | public void Accept(IConfigurableBus setup) 18 | { 19 | setup.ConfigurePublishing(p => p.DefaultPublishPipeline(new ParallelBlockingPublisher())); 20 | setup.AddResolver(new CompositeSubscription()); 21 | setup.ConfigureSubscribing(cs => cs.ApplyOnNewSubscription(new ShapeToDispose())); 22 | setup.AddService(TaskScheduler.FromCurrentSynchronizationContext()); 23 | } 24 | } 25 | 26 | /// 27 | /// Setup the Bus with this on the UI thread (Important!). 28 | /// A parallel publisher is used that publishes messages in parallel. With this setup 29 | /// publishing does NOT block. Exceptions will become available once all subscriptions are done processing the message as message. 30 | /// This setup allows you to call when doing a subscription. 31 | /// 32 | public class AsyncRichClientFrontend : AsyncConfiguration 33 | { 34 | public override void Accept(IConfigurableBus setup) 35 | { 36 | base.Accept(setup); 37 | setup.AddService(TaskScheduler.FromCurrentSynchronizationContext()); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /MemBus/IBus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using MemBus.Subscribing; 4 | using MemBus.Messages; 5 | using MemBus.Publishing; 6 | 7 | namespace MemBus 8 | { 9 | /// 10 | /// Descibes just the publishing features of MemBus. The itf inherits from this one. 11 | /// 12 | public interface IPublisher 13 | { 14 | /// 15 | /// Publish a message 16 | /// 17 | void Publish(object message); 18 | 19 | /// 20 | /// Publishes an observable that provides objects of type M. 21 | /// Once the observer that MemBus attaches to this observable receives the call to "OnCompleted", the message will be sent. 22 | /// An exception will be mapped to a message of type . 23 | /// Disposing the returned IDisposable will remove the generated observer from the observable. 24 | /// 25 | IDisposable Publish(IObservable observable); 26 | 27 | /// 28 | /// Publish a message in an awaitable fashion. This will short-circuit the conventional 29 | /// publishing and subscribing components and uses an awaitable version of the 30 | /// 31 | /// 32 | Task PublishAsync(object message); 33 | 34 | } 35 | 36 | /// 37 | /// Descibes just the subscribing features of MemBus. The itf inherits from this one. 38 | /// 39 | public interface ISubscriber 40 | { 41 | /// 42 | /// Subscribe anything matching the signature 43 | /// 44 | IDisposable Subscribe(Action subscription); 45 | 46 | /// 47 | /// Subscribe any methods defined on the subscriber. You need to have added and configured the 48 | /// for this to work 49 | /// 50 | IDisposable Subscribe(object subscriber); 51 | 52 | /// 53 | /// Subscrive a given subscription and provide a customization to the generated subscription. 54 | /// See for more information like available implementations. Note that regardless of your customization, 55 | /// 56 | IDisposable Subscribe(Action subscription, ISubscriptionShaper customization); 57 | 58 | /// 59 | /// Obtain an instance to observe any incoming objects of te desired types 60 | /// 61 | IObservable Observe(); 62 | } 63 | 64 | /// 65 | /// The IBus brings together the and capabilities of MemBus. 66 | /// You obtain an instance via the class. 67 | /// 68 | public interface IBus : IDisposable, IPublisher, ISubscriber 69 | { 70 | } 71 | } -------------------------------------------------------------------------------- /MemBus/ISubscription.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MemBus 4 | { 5 | /// 6 | /// Basic ISubscription interface 7 | /// 8 | public interface ISubscription 9 | { 10 | /// 11 | /// Accept the message 12 | /// 13 | void Push(object message); 14 | 15 | /// 16 | /// State whether this type of message is handled 17 | /// 18 | bool Handles(Type messageType); 19 | } 20 | 21 | /// 22 | /// A disposable subscription 23 | /// 24 | public interface IDisposableSubscription : ISubscription 25 | { 26 | /// 27 | /// Get a handle to be able to dispose this subscription 28 | /// 29 | /// 30 | IDisposable GetDisposer(); 31 | 32 | /// 33 | /// State whether this subscription is disposed 34 | /// 35 | bool IsDisposed { get; } 36 | 37 | /// 38 | /// Raise event when you get disposed 39 | /// 40 | event EventHandler Disposed; 41 | } 42 | } -------------------------------------------------------------------------------- /MemBus/ISubscriptionResolver.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace MemBus 4 | { 5 | /// 6 | /// Describes a component that for a given message returns subscriptions that want to handle it 7 | /// 8 | public interface ISubscriptionResolver 9 | { 10 | /// 11 | /// Returns a list of subscriptions that would handle this message 12 | /// 13 | IEnumerable GetSubscriptionsFor(object message); 14 | 15 | /// 16 | /// Adds a subscription. the return value signals whether the resolver did in fact accept the subscription 17 | /// 18 | bool Add(ISubscription subscription); 19 | } 20 | } -------------------------------------------------------------------------------- /MemBus/IoCBasedResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Threading; 6 | using MemBus.Subscribing; 7 | 8 | namespace MemBus 9 | { 10 | internal class IoCBasedResolver : ISubscriptionResolver 11 | { 12 | private readonly IocAdapter _adapter; 13 | private readonly Type _handlerType; 14 | private readonly Func _messageTypeResolver; 15 | private readonly Dictionary _typeCache = new Dictionary(); 16 | private readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim(); 17 | 18 | public IoCBasedResolver(IocAdapter adapter, Type handlerType, Func messageTypeResolver) 19 | { 20 | _adapter = adapter; 21 | _handlerType = handlerType; 22 | _messageTypeResolver = messageTypeResolver; 23 | } 24 | 25 | public IEnumerable GetSubscriptionsFor(object message) 26 | { 27 | var handlesType = ConstructHandlesType(message.GetType()); 28 | var mi = handlesType.GetRuntimeMethods().First(); 29 | return _adapter.GetAllInstances(handlesType) 30 | .Select(mi.ConstructSubscription); 31 | } 32 | 33 | private Type ConstructHandlesType(Type messageType) 34 | { 35 | try 36 | { 37 | _rwLock.EnterUpgradeableReadLock(); 38 | if (!_typeCache.ContainsKey(messageType)) 39 | { 40 | try 41 | { 42 | _rwLock.EnterWriteLock(); 43 | _typeCache[messageType] = _handlerType.MakeGenericType(_messageTypeResolver(messageType)); 44 | } 45 | finally 46 | { 47 | _rwLock.ExitWriteLock(); 48 | } 49 | } 50 | return _typeCache[messageType]; 51 | } 52 | finally 53 | { 54 | _rwLock.ExitUpgradeableReadLock(); 55 | } 56 | } 57 | 58 | public bool Add(ISubscription subscription) 59 | { 60 | // We just resolve here, no adding. 61 | return false; 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /MemBus/IocAdapter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using MemBus.Configurators; 4 | 5 | namespace MemBus 6 | { 7 | /// 8 | /// Implement to be used in conjunction with . This allows you to inject your container of choice into 9 | /// the resolution of subscriptions 10 | /// 11 | public interface IocAdapter 12 | { 13 | /// 14 | /// Return all instances that implement the desired type 15 | /// 16 | IEnumerable GetAllInstances(Type desiredType); 17 | } 18 | } -------------------------------------------------------------------------------- /MemBus/MemBus.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Pub/Sub in-memory with all parts configurable 4 | Frank Quednau 5 | net45;net46;netstandard1.6;netstandard2.0 6 | MemBus 7 | MemBus 8 | publish;subscribe;bus;messaging 9 | netstandard +net45+ net46 10 | http://realfiction.net/public/assets/bus.png 11 | http://github.com/flq/MemBus 12 | http://www.opensource.org/licenses/apache2.0 13 | 1.6.1 14 | false 15 | false 16 | false 17 | false 18 | false 19 | false 20 | false 21 | false 22 | True 23 | 4.0.1 24 | 4.0.1 25 | bin\Debug\$(TargetFramework)\MemBus.xml 26 | CS1591 27 | 7.2 28 | 29 | 30 | TRACE;DEBUG;NETSTANDARD1_6 31 | 32 | -------------------------------------------------------------------------------- /MemBus/MessageInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MemBus.Setup; 3 | 4 | namespace MemBus 5 | { 6 | 7 | /// 8 | /// Used to specify filters in the context of and 9 | /// 10 | public struct MessageInfo 11 | { 12 | private readonly object _message; 13 | 14 | internal MessageInfo(object message) 15 | { 16 | _message = message ?? throw new ArgumentNullException(nameof(message)); 17 | } 18 | 19 | /// 20 | /// True if the message associated with this info is asssignable to T 21 | /// 22 | public bool IsType() => _message is T; 23 | 24 | /// 25 | /// Like IsType but allows you ti supply additional matching 26 | /// 27 | public bool IsType(Func additionalMatch) 28 | { 29 | if (additionalMatch == null) throw new ArgumentNullException(nameof(additionalMatch)); 30 | return IsType() && additionalMatch((T) _message); 31 | } 32 | 33 | /// 34 | /// The Name of this message's type 35 | /// 36 | public string Name => _message.GetType().Name; 37 | 38 | } 39 | } -------------------------------------------------------------------------------- /MemBus/MessageObservable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MemBus 4 | { 5 | public class MessageObservable : IObservable 6 | { 7 | private readonly ISubscriber subscriber; 8 | 9 | public MessageObservable(ISubscriber subscriber) 10 | { 11 | this.subscriber = subscriber; 12 | } 13 | 14 | public IDisposable Subscribe(IObserver observer) 15 | { 16 | if (observer == null) throw new ArgumentNullException(nameof(observer)); 17 | return subscriber.Subscribe(observer.OnNext); 18 | } 19 | 20 | } 21 | } -------------------------------------------------------------------------------- /MemBus/Messages/ExceptionOccurred.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace MemBus.Messages 5 | { 6 | /// 7 | /// Some configurations send out this message if during publishing of a Message an exception occurs 8 | /// 9 | public class ExceptionOccurred 10 | { 11 | /// 12 | /// The caotured exception 13 | /// 14 | public Exception Exception { get; private set; } 15 | 16 | /// 17 | /// ctor with the exception to be captured 18 | /// 19 | /// 20 | public ExceptionOccurred(Exception exception) 21 | { 22 | Exception = exception; 23 | } 24 | 25 | /// 26 | /// Some exception Information 27 | /// 28 | public override string ToString() 29 | { 30 | var b = new StringBuilder(); 31 | 32 | if (Exception is AggregateException) 33 | { 34 | var ax = (AggregateException) Exception; 35 | foreach (var x in ax.InnerExceptions) 36 | PrintException(b, x); 37 | } 38 | else 39 | PrintException(b, Exception); 40 | 41 | return b.ToString(); 42 | } 43 | 44 | private static void PrintException(StringBuilder b, Exception x) 45 | { 46 | b.AppendFormat("Type of Exception: {0}", x.GetType().Name); 47 | b.AppendFormat("Message: {0}", x.Message); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /MemBus/Messages/MessageStreamCompleted.cs: -------------------------------------------------------------------------------- 1 | namespace MemBus.Messages 2 | { 3 | /// 4 | /// Sent if an observable that enters MemBus via 5 | /// notifies observables via "OnCompleted". 6 | /// 7 | // ReSharper disable once UnusedTypeParameter 8 | public class MessageStreamCompleted 9 | { 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /MemBus/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | 4 | [assembly: AssemblyTitle("MemBus")] 5 | [assembly: AssemblyDescription("Publish Subscribe in-memory")] 6 | [assembly: AssemblyConfiguration("")] 7 | [assembly: AssemblyCompany("Frank Quednau")] 8 | [assembly: AssemblyProduct("MemBus")] 9 | [assembly: AssemblyCopyright("Apache 2.0 licensed 2017 realfiction (F Quednau)")] 10 | 11 | [assembly: InternalsVisibleTo("MemBus.Tests")] 12 | [assembly: InternalsVisibleTo("MemBus.Tests.Performance")] 13 | 14 | [assembly: AssemblyVersion("4.0.1")] 15 | [assembly: AssemblyFileVersion("4.0.1")] 16 | -------------------------------------------------------------------------------- /MemBus/Publishing/DeferredPublishPipelineMember.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MemBus.Publishing 4 | { 5 | /// 6 | /// Use this class if you cannot instantiate the actual pipeline member at the time you setup the pipeline 7 | /// Provide a factory method to a pipeline member that will be called and used once this member is called 8 | /// during publish processing 9 | /// 10 | /// Any class implementing 11 | public class DeferredPublishPipelineMember : IPublishPipelineMember where T : IPublishPipelineMember 12 | { 13 | private readonly Func _actualPipelineMember; 14 | 15 | /// 16 | /// ctor with the factory method to obtain the "real" 17 | /// 18 | /// 19 | public DeferredPublishPipelineMember(Func actualPipelineMember) 20 | { 21 | _actualPipelineMember = actualPipelineMember; 22 | } 23 | 24 | void IPublishPipelineMember.LookAt(PublishToken token) 25 | { 26 | var member = _actualPipelineMember(); 27 | member.LookAt(token); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /MemBus/Publishing/FireAndForgetPublisher.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | 4 | namespace MemBus.Publishing 5 | { 6 | /// 7 | /// Publisher of messages for maximum througput. If any exceptions occur, they will not be honoured in any way. 8 | /// 9 | public class FireAndForgetPublisher : IPublishPipelineMember 10 | { 11 | private readonly TaskFactory _taskMaker = new TaskFactory(); 12 | 13 | public void LookAt(PublishToken token) 14 | { 15 | token.Subscriptions 16 | .Select(s => _taskMaker.StartNew(() => s.Push(token.Message))) 17 | .ToArray(); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /MemBus/Publishing/IPublishPipelineMember.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using MemBus.Support; 3 | 4 | namespace MemBus.Publishing 5 | { 6 | /// 7 | /// Defines a single member of a publishing pipeline. 8 | /// If you introduce a new publish pipeline member, 9 | /// you can additionally implement 10 | /// to get access to the instance. 11 | /// 12 | public interface IPublishPipelineMember 13 | { 14 | void LookAt(PublishToken token); 15 | } 16 | 17 | /// 18 | /// Defines a member of a publishing pipeline 19 | /// that can be awaited 20 | /// 21 | public interface IAsyncPublishPipelineMember 22 | { 23 | /// 24 | /// Inspect the publish token 25 | /// 26 | Task LookAtAsync(PublishToken token); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /MemBus/Publishing/ObservableRelay.cs: -------------------------------------------------------------------------------- 1 | using MemBus.Messages; 2 | using System; 3 | 4 | namespace MemBus.Publishing 5 | { 6 | internal class ObservableRelay 7 | { 8 | readonly IPublisher _publisher; 9 | 10 | public ObservableRelay(IPublisher publisher) 11 | { 12 | _publisher = publisher; 13 | } 14 | 15 | public IDisposable Connect(IObservable observable) 16 | { 17 | var obs = new BusRelayObserver(_publisher); 18 | return observable.Subscribe(obs); 19 | } 20 | 21 | 22 | 23 | private class BusRelayObserver : IObserver 24 | { 25 | readonly IPublisher _publisher; 26 | 27 | public BusRelayObserver(IPublisher publisher) 28 | { 29 | _publisher = publisher; 30 | } 31 | 32 | public void OnCompleted() 33 | { 34 | _publisher.Publish(new MessageStreamCompleted()); 35 | } 36 | 37 | public void OnError(Exception error) 38 | { 39 | _publisher.Publish(new ExceptionOccurred(error)); 40 | } 41 | 42 | public void OnNext(M value) 43 | { 44 | _publisher.Publish(value); 45 | } 46 | } 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /MemBus/Publishing/ParallelBlockingPublisher.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using System.Linq; 3 | using MemBus.Support; 4 | 5 | namespace MemBus.Publishing 6 | { 7 | /// 8 | /// Calls subscribers in parallel but blocks until all subscriptions return. 9 | /// 10 | public class ParallelBlockingPublisher : IPublishPipelineMember, IRequireBus 11 | { 12 | private readonly TaskFactory _taskMaker = new TaskFactory(); 13 | private IBus _bus; 14 | 15 | public void LookAt(PublishToken token) 16 | { 17 | var tasks = token.Subscriptions.Select(s => _taskMaker.StartNew(() => s.Push(token.Message))).ToArray(); 18 | Task.WaitAll(tasks); 19 | tasks.PublishExceptionMessages(_bus); 20 | } 21 | 22 | void IRequireBus.AddBus(IBus bus) 23 | { 24 | _bus = bus; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /MemBus/Publishing/ParallelNonBlockingPublisher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using MemBus.Messages; 5 | using MemBus.Support; 6 | 7 | namespace MemBus.Publishing 8 | { 9 | /// 10 | /// Publisher of messages that publishes in parallel and does not wait for all handlers to return. 11 | /// When all subscriptions have done their work, any exceptions will be collected and put on the bus 12 | /// as messages. 13 | /// 14 | public class ParallelNonBlockingPublisher : IPublishPipelineMember, IRequireBus 15 | { 16 | private readonly TaskFactory taskMaker = new TaskFactory(); 17 | private IBus _bus; 18 | 19 | 20 | public void LookAt(PublishToken token) 21 | { 22 | var tasks = token.Subscriptions.Select(s => taskMaker.StartNew(() => s.Push(token.Message))).ToArray(); 23 | if (tasks.Length == 0) 24 | return; 25 | taskMaker.ContinueWhenAll(tasks, 26 | ts => 27 | { 28 | //TODO: How to catch this exception? Seems to go to Nirvana... 29 | if (token.Message is ExceptionOccurred && ts.Any(t=>t.IsFaulted)) 30 | throw new MemBusException("Possible infinite messaging cycle since handling ExceptionOccurred has produced unhandled exceptions!"); 31 | ts.PublishExceptionMessages(_bus); 32 | }); 33 | } 34 | 35 | void IRequireBus.AddBus(IBus bus) 36 | { 37 | if (bus == null) 38 | throw new InvalidOperationException("This publisher requires a bus for exception propagation"); 39 | _bus = bus; 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /MemBus/Publishing/PublishChain.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace MemBus.Publishing 5 | { 6 | internal class DefaultPublishChain : PublishChain 7 | { 8 | public DefaultPublishChain(IEnumerable members) : base(_ => true, members) 9 | { 10 | } 11 | } 12 | 13 | internal class PublishChain 14 | { 15 | private readonly Func _match; 16 | private readonly List _pipelineMembers = new List(); 17 | 18 | public PublishChain(Func match) : this(match, new IPublishPipelineMember[] {}) {} 19 | 20 | public PublishChain(Func match, IEnumerable members) 21 | { 22 | _match = match; 23 | _pipelineMembers.AddRange(members); 24 | } 25 | 26 | public bool Handles(MessageInfo msgInfo) 27 | { 28 | return _match(msgInfo); 29 | } 30 | 31 | public void LookAt(PublishToken token) 32 | { 33 | for (var i = 0; i <= _pipelineMembers.Count - 1; i++) 34 | { 35 | if (token.Cancel) 36 | break; 37 | _pipelineMembers[i].LookAt(token); 38 | } 39 | } 40 | 41 | public void Add(IPublishPipelineMember publishPipelineMember) 42 | { 43 | _pipelineMembers.Add(publishPipelineMember); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /MemBus/Publishing/PublishChainCasing.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using MemBus.Setup; 5 | using MemBus.Support; 6 | using System.Linq; 7 | 8 | namespace MemBus.Publishing 9 | { 10 | internal class PublishChainCasing : IConfigurablePublishing, IDisposable 11 | { 12 | private readonly List _pipelines = new List(); 13 | private readonly IBus _bus; 14 | 15 | public PublishChainCasing(IBus bus) 16 | { 17 | _bus = bus; 18 | } 19 | 20 | public void LookAt(PublishToken token) 21 | { 22 | var info = new MessageInfo(token.Message); 23 | for (int i = _pipelines.Count - 1; i >= 0; i--) //Backwards as we keep the default at index 0 24 | { 25 | if (!_pipelines[i].Handles(info)) 26 | continue; 27 | _pipelines[i].LookAt(token); 28 | break; 29 | } 30 | } 31 | 32 | public async Task LookAtAsync(PublishToken token) 33 | { 34 | IAsyncPublishPipelineMember publisher = new SequentialPublisher(); 35 | await publisher.LookAtAsync(token); 36 | } 37 | 38 | 39 | void IConfigurablePublishing.DefaultPublishPipeline(params IPublishPipelineMember[] publishPipelineMembers) 40 | { 41 | foreach (var m in publishPipelineMembers.OfType()) 42 | m.AddBus(_bus); 43 | if (_pipelines.Count > 0 && _pipelines[0] is DefaultPublishChain) 44 | _pipelines.RemoveAt(0); 45 | _pipelines.Insert(0, new DefaultPublishChain(publishPipelineMembers)); 46 | } 47 | 48 | IConfigurePipeline IConfigurablePublishing.MessageMatch(Func match) 49 | { 50 | var cP = new ConfigurePipeline(match, _bus); 51 | _pipelines.Add(cP.Provider); 52 | return cP; 53 | } 54 | 55 | void IConfigurablePublishing.ConfigureWith() 56 | { 57 | var t = new T(); 58 | t.Accept(this); 59 | } 60 | 61 | private class ConfigurePipeline : IConfigurePipeline 62 | { 63 | private readonly IBus _bus; 64 | private readonly PublishChain _publishChain; 65 | 66 | public ConfigurePipeline(Func match, IBus bus) 67 | { 68 | _bus = bus; 69 | _publishChain = new PublishChain(match); 70 | } 71 | 72 | public PublishChain Provider { get { return _publishChain; } } 73 | 74 | public void ConfigureWith() where T : ISetup, new() 75 | { 76 | var t = new T(); 77 | t.Accept(this); 78 | } 79 | 80 | public void PublishPipeline(params IPublishPipelineMember[] publishPipelineMembers) 81 | { 82 | foreach (var m in publishPipelineMembers) 83 | { 84 | m.Being(_ => _.AddBus(_bus)); 85 | Provider.Add(m); 86 | } 87 | } 88 | } 89 | 90 | public void Dispose() 91 | { 92 | _pipelines.Clear(); 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /MemBus/Publishing/PublishToken.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace MemBus.Publishing 4 | { 5 | public class PublishToken 6 | { 7 | public object Message { get; private set; } 8 | public IEnumerable Subscriptions { get; private set; } 9 | 10 | /// 11 | /// When set to true, the subsequent members in the publish pipeline will not be called anymore 12 | /// 13 | public bool Cancel { get; set; } 14 | 15 | public PublishToken(object message, IEnumerable subscriptions) 16 | { 17 | Message = message; 18 | Subscriptions = subscriptions; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /MemBus/Publishing/SequentialPublisher.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace MemBus.Publishing 4 | { 5 | /// 6 | /// This is the most simple publisher that works like event handlers: All subscriptions are called in sequence. 7 | /// If any subscription throws an exception the chain is broken. 8 | /// 9 | public class SequentialPublisher : IPublishPipelineMember, IAsyncPublishPipelineMember 10 | { 11 | public void LookAt(PublishToken token) 12 | { 13 | foreach (var s in token.Subscriptions) 14 | s.Push(token.Message); 15 | } 16 | 17 | #pragma warning disable 1998 //This is just a route through method, so it's OK if this part does run synchronously 18 | public async Task LookAtAsync(PublishToken token) 19 | { 20 | await Task.Run(() => LookAt(token)); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /MemBus/RxEnabledObservable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MemBus 4 | { 5 | /// 6 | /// Helps you in separating a possibly complex Rx-based Observable query 7 | /// to its own class 8 | /// 9 | public class RxEnabledObservable : IObservable 10 | { 11 | private readonly IBus _bus; 12 | 13 | public RxEnabledObservable(IBus bus) 14 | { 15 | _bus = bus; 16 | } 17 | 18 | public IDisposable Subscribe(IObserver observer) 19 | { 20 | if (observer == null) throw new ArgumentNullException("observer"); 21 | var o = constructObservable(new MessageObservable(_bus)); 22 | return o.Subscribe(observer); 23 | } 24 | 25 | protected virtual IObservable constructObservable(IObservable startingPoint) 26 | { 27 | return startingPoint; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /MemBus/Setup/AdHocConfigurator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MemBus.Setup 4 | { 5 | internal class AdHocConfigurator : ISetup 6 | { 7 | private readonly Action _action; 8 | 9 | public AdHocConfigurator(Action action) 10 | { 11 | _action = action; 12 | } 13 | 14 | public void Accept(T setup) 15 | { 16 | _action(setup); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /MemBus/Setup/BusSetup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using MemBus.Configurators; 4 | using MemBus.Setup; 5 | using MemBus.Subscribing; 6 | 7 | // ReSharper disable CheckNamespace 8 | namespace MemBus 9 | { 10 | /// 11 | /// Your main entry point to set up an instance. Use the different Apply overloads and 12 | /// at the end call 13 | /// 14 | public class BusSetup 15 | { 16 | private readonly List> _configurators = new List>(); 17 | 18 | private BusSetup() { } 19 | 20 | /// 21 | /// Apply an arbitrary number of of instances. 22 | /// Examples are or 23 | /// 24 | public BusSetup Apply(params ISetup[] configurators) 25 | { 26 | _configurators.AddRange(configurators); 27 | return this; 28 | } 29 | 30 | 31 | /// 32 | /// Apply variant where you specify a configurator as type, and others as instance 33 | /// 34 | public BusSetup Apply(params ISetup[] configurators) where T : ISetup, new() 35 | { 36 | _configurators.Add(new T()); 37 | return Apply(configurators); 38 | } 39 | 40 | /// 41 | /// Apply a configurator that requires additional configuration which you can provide through the additionalConfig 42 | /// parameter. One example for this pattern is the 43 | /// 44 | public BusSetup Apply(Action additionalConfig) where T : ISetup, new() 45 | { 46 | var t = new T(); 47 | additionalConfig(t); 48 | return Apply(t); 49 | } 50 | 51 | /// 52 | /// Directly access the configuration interface of the bus. 53 | /// 54 | public BusSetup Apply(Action adHocConfig) 55 | { 56 | var t = new AdHocConfigurator(adHocConfig); 57 | return Apply(t); 58 | } 59 | 60 | /// 61 | /// Once you have applied a number of customizations, call this to obtain the instance to be used by your App. 62 | /// 63 | public IBus Construct() 64 | { 65 | var bus = new Bus(); 66 | Accept(bus); 67 | return bus; 68 | } 69 | 70 | /// 71 | /// Start with a configuration setup like e.g. or 72 | /// 73 | public static BusSetup StartWith(params ISetup[] configurators) where T : ISetup, new() 74 | { 75 | return new BusSetup().Apply(configurators); 76 | } 77 | 78 | /// 79 | /// Start with some Ad-Hoc configuration 80 | /// 81 | public static BusSetup StartWith(Action configure) where T : ISetup, new() 82 | { 83 | return StartWith(new AdHocConfigurator(configure)); 84 | } 85 | 86 | /// 87 | /// Apply two configurators as type arguments 88 | /// 89 | public static BusSetup StartWith(params ISetup[] configurators) 90 | where T1 : ISetup, new() 91 | where T2 : ISetup, new() 92 | { 93 | return new BusSetup().Apply().Apply(configurators); 94 | } 95 | 96 | private void Accept(IConfigurableBus configurableBus) 97 | { 98 | foreach (var c in _configurators) 99 | c.Accept(configurableBus); 100 | } 101 | } 102 | } 103 | // ReSharper restore CheckNamespace -------------------------------------------------------------------------------- /MemBus/Setup/IConfigurableBus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MemBus.Setup 4 | { 5 | public interface IConfigurableSubscriber 6 | { 7 | void ConfigureSubscribing(Action configure); 8 | void AddResolver(ISubscriptionResolver resolver); 9 | void AddSubscription(ISubscription subscription); 10 | } 11 | 12 | public interface IConfigurableBus : IConfigurableSubscriber 13 | { 14 | void ConfigurePublishing(Action configure); 15 | void AddService(T service); 16 | } 17 | } -------------------------------------------------------------------------------- /MemBus/Setup/IConfigurablePublishing.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MemBus.Publishing; 3 | 4 | namespace MemBus.Setup 5 | { 6 | public interface IConfigurablePublishing 7 | { 8 | void ConfigureWith() where T : ISetup, new(); 9 | void DefaultPublishPipeline(params IPublishPipelineMember[] publishPipelineMembers); 10 | IConfigurePipeline MessageMatch(Func match); 11 | } 12 | 13 | public interface IConfigurePipeline 14 | { 15 | void ConfigureWith() where T : ISetup, new(); 16 | void PublishPipeline(params IPublishPipelineMember[] publishPipelineMembers); 17 | } 18 | } -------------------------------------------------------------------------------- /MemBus/Setup/IConfigurableSubscribing.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MemBus.Subscribing; 3 | 4 | namespace MemBus.Setup 5 | { 6 | public interface IConfigurableSubscribing 7 | { 8 | /// 9 | /// These shapes are applied to subscription if none other matched during publishing of the pipeline 10 | /// 11 | void DefaultShapeOutwards(params ISubscriptionShaper[] shapers); 12 | 13 | /// 14 | /// Describe what shapes are applied to a subscription when a match is given to the message that is currently being published 15 | /// 16 | void MessageMatch(Func match, Action configure); 17 | 18 | /// 19 | /// These shapes are applied directly when introducing a subscription to the Bus 20 | /// 21 | void ApplyOnNewSubscription(params ISubscriptionShaper[] shapers); 22 | } 23 | 24 | public interface IConfigureSubscription 25 | { 26 | void ShapeOutwards(params ISubscriptionShaper[] shapers); 27 | } 28 | } -------------------------------------------------------------------------------- /MemBus/Setup/ISetup.cs: -------------------------------------------------------------------------------- 1 | namespace MemBus.Setup 2 | { 3 | /// 4 | /// An interface that follws an extension pattern in which you accept an instance of the specified type 5 | /// 6 | public interface ISetup 7 | { 8 | /// 9 | /// Accept an instance of type T 10 | /// 11 | void Accept(T setup); 12 | } 13 | } -------------------------------------------------------------------------------- /MemBus/Subscriber.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using MemBus.Setup; 4 | using MemBus.Subscribing; 5 | using MemBus.Support; 6 | 7 | namespace MemBus 8 | { 9 | public class Subscriber : ISubscriber, IDisposable, IConfigurableSubscriber, ISubscriptionResolver 10 | { 11 | private readonly IServices _services; 12 | private readonly SubscriptionPipeline _subscriptionPipeline; 13 | private readonly CompositeResolver _resolvers = new CompositeResolver(); 14 | private bool _isDisposed; 15 | 16 | public Subscriber(IServices services) 17 | { 18 | _services = services; 19 | _subscriptionPipeline = new SubscriptionPipeline(services); 20 | } 21 | 22 | public IDisposable Subscribe(Action subscription) 23 | { 24 | return Subscribe(subscription, _subscriptionPipeline.GetIntroductionShape()); 25 | 26 | } 27 | 28 | public IDisposable Subscribe(object subscriber) 29 | { 30 | var svc = _services.Get(); 31 | if (svc == null) 32 | throw new InvalidOperationException( 33 | "No subscription adapter rules were formulated. Apply the FlexibleSubscribeAdapter to state rules how some instance may be wired up into MemBus."); 34 | var disposeContainer = svc.WireUpSubscriber(_resolvers, subscriber); 35 | return disposeContainer; 36 | 37 | } 38 | 39 | public IDisposable Subscribe(Action subscription, ISubscriptionShaper customization) 40 | { 41 | CheckDisposed(); 42 | var sShapeAgg = new SubscriptionShaperAggregate() { customization }; 43 | sShapeAgg.Add(new ShapeToDispose()); 44 | var sub = sShapeAgg.EnhanceSubscription(new MethodInvocation(subscription)); 45 | _resolvers.Add(sub); 46 | return sub.TryReturnDisposerOfSubscription(); 47 | } 48 | 49 | public IObservable Observe() 50 | { 51 | CheckDisposed(); 52 | return new MessageObservable(this); 53 | } 54 | 55 | public void Dispose() 56 | { 57 | _resolvers.Dispose(); 58 | _subscriptionPipeline.Dispose(); 59 | _isDisposed = true; 60 | } 61 | 62 | void IConfigurableSubscriber.ConfigureSubscribing(Action configure) 63 | { 64 | CheckDisposed(); 65 | configure(_subscriptionPipeline); 66 | } 67 | 68 | void IConfigurableSubscriber.AddResolver(ISubscriptionResolver resolver) 69 | { 70 | CheckDisposed(); 71 | resolver.Being(_ => _.AddServices(_services)); 72 | _resolvers.Add(resolver); 73 | } 74 | 75 | void IConfigurableSubscriber.AddSubscription(ISubscription subscription) 76 | { 77 | ((ISubscriptionResolver) this).Add(subscription); 78 | } 79 | 80 | public IEnumerable GetSubscriptionsFor(object message) 81 | { 82 | var subs = _resolvers.GetSubscriptionsFor(message); 83 | subs = _subscriptionPipeline.Shape(subs, message); 84 | return subs; 85 | } 86 | 87 | bool ISubscriptionResolver.Add(ISubscription subscription) 88 | { 89 | CheckDisposed(); 90 | return _resolvers.Add(subscription); 91 | } 92 | 93 | private void CheckDisposed() 94 | { 95 | if (_isDisposed) 96 | throw new ObjectDisposedException("Publisher"); 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /MemBus/Subscribing/Adapter/ConstructSubscriptionExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | using System.Linq; 6 | 7 | namespace MemBus.Subscribing 8 | { 9 | public static class ConstructSubscriptionExtension 10 | { 11 | public static ISubscription ConstructSubscription(this MethodInfo info, object target) 12 | { 13 | var parameterType = info.GetParameters()[0].ParameterType; 14 | var fittingDelegateType = typeof(Action<>).MakeGenericType(parameterType); 15 | var p = Expression.Parameter(parameterType); 16 | var call = Expression.Call(Expression.Constant(target), info, p); 17 | var @delegate = Expression.Lambda(fittingDelegateType, call, p); 18 | 19 | var fittingMethodSubscription = typeof(MethodInvocation<>).MakeGenericType(parameterType); 20 | var sub = Activator.CreateInstance(fittingMethodSubscription, @delegate.Compile()); 21 | 22 | return (ISubscription)sub; 23 | } 24 | 25 | public static ISubscription ConstructPublishingSubscription(this MethodInfo targetMethod, object target, IPublisher publisher) 26 | { 27 | var parameterType = targetMethod.GetParameters()[0].ParameterType; 28 | var fittingDelegateType = typeof(Func<,>).MakeGenericType(parameterType,typeof(object)); 29 | var p = Expression.Parameter(parameterType); 30 | Expression call = Expression.Call(Expression.Constant(target), targetMethod, p); 31 | if (!targetMethod.ReturnType.GetTypeInfo().IsClass) 32 | { 33 | call = Expression.Convert(call, typeof(object)); 34 | } 35 | var @delegate = Expression.Lambda(fittingDelegateType, call, p); 36 | 37 | var fittingMethodSubscription = typeof(PublishingMethodInvocation<>).MakeGenericType(parameterType); 38 | var sub = Activator.CreateInstance(fittingMethodSubscription, @delegate.Compile(), publisher); 39 | 40 | return (ISubscription)sub; 41 | } 42 | 43 | public static IEnumerable ConstructSubscriptions(this IEnumerable infos, object target) 44 | { 45 | return infos.Select(i => i.ConstructSubscription(target)); 46 | } 47 | 48 | public static IEnumerable ConstructPublishingSubscriptions(this IEnumerable infos, object target, IPublisher publisher) 49 | { 50 | return infos.Select(i => i.ConstructPublishingSubscription(target, publisher)); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /MemBus/Subscribing/Adapter/FlexibleSubscribeAdapter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using MemBus.Setup; 5 | using System.Linq; 6 | using MemBus.Support; 7 | 8 | namespace MemBus.Subscribing 9 | { 10 | internal interface IAdapterServices 11 | { 12 | IDisposable WireUpSubscriber(ISubscriptionResolver subscriptionResolver, object subscriber); 13 | } 14 | 15 | /// 16 | /// Add this through the to your configuration to support subscribing objects with the 17 | /// , overload. 18 | /// 19 | public class FlexibleSubscribeAdapter : ISetup, IAdapterServices 20 | { 21 | private bool _configurationAvailable; 22 | private readonly MessageEndpointsBuilder _builder = new MessageEndpointsBuilder(); 23 | 24 | 25 | void ISetup.Accept(IConfigurableBus setup) 26 | { 27 | if (!_configurationAvailable) 28 | throw new InvalidOperationException("No adapter rules were set up."); 29 | setup.AddService(this); 30 | 31 | _builder.SetPublisher((IBus)setup); 32 | } 33 | 34 | /// 35 | /// Look at an object and look for methods with the provided name. The method must NOT be void 36 | /// and accept a single parameter. The returning object will be treated as a message and subsequently be published 37 | /// 38 | public FlexibleSubscribeAdapter RegisterMethods(string methodName) 39 | { 40 | AddToScanners(new MethodScanner(methodName)); 41 | return this; 42 | } 43 | 44 | /// 45 | /// Look at an object and scan the available methods. For those where the methodSelector returns true, 46 | /// subscriptions will be created and registered in MemBus. The methods are already pre-filtered for those 47 | /// accepting a single parameter, being public, as well as allowing only the METHODS DECLARED ON THE TYPE OF THE INSPECTED OBJECT. 48 | /// Additionally, if your subscribing object implements , any method that looks like the implementation 49 | /// of said interface will be ignored 50 | /// This function does not make a difference between void methods and methods returning a value. 51 | /// These will be registered as publishing methods or simple subscriptions. 52 | /// 53 | /// the method selector predicate 54 | public FlexibleSubscribeAdapter RegisterMethods(Func methodSelector) 55 | { 56 | AddToScanners(new MethodScanner(methodSelector)); 57 | return this; 58 | } 59 | 60 | /// 61 | /// Look at an object and check it for interfaces. An interface should adhere to the following rules: 62 | /// Interface should define only one void method with a single parameter. 63 | /// The interface may be generic and can be implemented multiple times. 64 | /// 65 | public FlexibleSubscribeAdapter ByInterface(Type interfaceType) 66 | { 67 | AddToScanners(new InterfaceBasedBuilder(interfaceType)); 68 | return this; 69 | } 70 | 71 | private IEnumerable SubscriptionsFor(object subscriber) 72 | { 73 | return _builder.BuildSubscriptions(subscriber); 74 | } 75 | 76 | 77 | IDisposable IAdapterServices.WireUpSubscriber(ISubscriptionResolver subscriptionResolver, object subscriber) 78 | { 79 | var disposeShape = new ShapeToDispose(); 80 | var disposeContainer = new DisposeContainer(); 81 | foreach (var disposable in SubscriptionsFor(subscriber).Select(disposeShape.EnhanceSubscription)) 82 | { 83 | subscriptionResolver.Add(disposable); 84 | disposeContainer.Add(((IDisposableSubscription) disposable).GetDisposer()); 85 | } 86 | 87 | PushDisposerToSubscriberIfPossible(subscriber, disposeContainer); 88 | 89 | return disposeContainer; 90 | } 91 | 92 | private void AddToScanners(IMethodInfoScanner builder) 93 | { 94 | _builder.AddScanner(builder); 95 | _configurationAvailable = true; 96 | } 97 | 98 | private static void PushDisposerToSubscriberIfPossible(object subscriber, DisposeContainer disposeContainer) 99 | { 100 | var disposeAcceptor = subscriber as IAcceptDisposeToken; 101 | if (disposeAcceptor != null) 102 | disposeAcceptor.Accept(disposeContainer); 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /MemBus/Subscribing/Adapter/IMethodInfoScanner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using MemBus.Support; 5 | 6 | namespace MemBus.Subscribing 7 | { 8 | 9 | internal interface IMethodInfoScanner 10 | { 11 | IEnumerable GetMethodInfos(object targetToAdapt); 12 | } 13 | 14 | /// 15 | /// Wraps a method info and contains the logic in what way said method is usable to MemBus. 16 | /// 17 | public class ClassifiedMethodInfo 18 | { 19 | public ClassifiedMethodInfo(MethodInfo methodInfo) 20 | { 21 | MethodInfo = methodInfo; 22 | Classifier = DeriveClassifier(); 23 | } 24 | 25 | public override bool Equals(object obj) 26 | { 27 | if (ReferenceEquals(null, obj)) return false; 28 | if (ReferenceEquals(this, obj)) return true; 29 | if (obj.GetType() != this.GetType()) return false; 30 | return Equals((ClassifiedMethodInfo) obj); 31 | } 32 | 33 | public override int GetHashCode() 34 | { 35 | return MethodInfo.GetHashCode(); 36 | } 37 | 38 | public MethodInfo MethodInfo { get; private set; } 39 | 40 | public MethodInfoClassifier Classifier { get; private set; } 41 | 42 | public static ClassifiedMethodInfo New(MethodInfo mi) 43 | { 44 | return new ClassifiedMethodInfo(mi); 45 | } 46 | 47 | protected bool Equals(ClassifiedMethodInfo other) 48 | { 49 | return MethodInfo.Equals(other.MethodInfo); 50 | } 51 | 52 | private MethodInfoClassifier DeriveClassifier() 53 | { 54 | var classifier = MethodInfoClassifier.Unusable; 55 | if (MethodHasParameter) 56 | { 57 | if (ParameterIsObservable) 58 | { 59 | if (ReturnTypeIsVoid) 60 | classifier = MethodInfoClassifier.ObservableSink; 61 | if (MethodReturnsObservable) 62 | classifier = MethodInfoClassifier.ObservableMap; 63 | } 64 | else 65 | { 66 | if (ReturnTypeIsVoid) 67 | classifier = MethodInfoClassifier.MessageSink; 68 | else 69 | classifier = MethodInfoClassifier.MessageMap; 70 | } 71 | } 72 | else if (MethodHasNoParameter && MethodReturnsObservable) 73 | { 74 | classifier = MethodInfoClassifier.ObservableSource; 75 | } 76 | return classifier; 77 | } 78 | 79 | private bool MethodReturnsObservable 80 | { 81 | get { return MethodInfo.ReturnType.IsConcreteObservable(); } 82 | } 83 | 84 | private bool ReturnTypeIsVoid 85 | { 86 | get { return MethodInfo.ReturnType == typeof (void); } 87 | } 88 | 89 | private bool ParameterIsObservable 90 | { 91 | get { return MethodInfo.GetParameters()[0].ParameterType.IsConcreteObservable(); } 92 | } 93 | 94 | private bool MethodHasParameter 95 | { 96 | get { return MethodInfo.GetParameters().Length == 1; } 97 | } 98 | 99 | private bool MethodHasNoParameter 100 | { 101 | get { return MethodInfo.GetParameters().Length == 0; } 102 | } 103 | } 104 | 105 | /// 106 | /// Further classifies a method info as to how it will be used by MemBus. 107 | /// Please note that where Observables are in use, the parameter type or return type *must* be of 108 | /// the interface type , not some derived type. 109 | /// 110 | public enum MethodInfoClassifier 111 | { 112 | /// 113 | /// Method is unusable by MemBus 114 | /// 115 | Unusable = 0, 116 | /// 117 | /// The method accepts messages and also returns something which should be interpreted as message. 118 | /// Note that if the return value implements IEnumerable, the return value will be enumerated and each element will be published as message. 119 | /// This is not recursive. 120 | /// 121 | MessageMap, 122 | /// 123 | /// The method accepts messages but returns nothing in response. 124 | /// 125 | MessageSink, 126 | /// 127 | /// The method accepts an observable and returns nothing. 128 | /// 129 | ObservableSink, 130 | /// 131 | /// The method accepts an observable and returns an observable. 132 | /// 133 | ObservableMap, 134 | /// 135 | /// The method accepts no parameter but returns an observable. 136 | /// 137 | ObservableSource 138 | } 139 | } -------------------------------------------------------------------------------- /MemBus/Subscribing/Adapter/InterfaceBasedBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using MemBus.Support; 6 | 7 | namespace MemBus.Subscribing 8 | { 9 | public class InterfaceBasedBuilder : IMethodInfoScanner 10 | { 11 | private readonly IMethodInfoScanner _innerBuilder; 12 | 13 | public InterfaceBasedBuilder(Type interfaceType) 14 | { 15 | if (interfaceType.GetTypeInfo().IsGenericTypeDefinition) 16 | _innerBuilder = new OpenInterfaceBuilder(interfaceType); 17 | else 18 | _innerBuilder = new ClosedInterfaceBuilder(interfaceType); 19 | 20 | } 21 | 22 | public IEnumerable GetMethodInfos(object targetToAdapt) 23 | { 24 | if (targetToAdapt == null) throw new ArgumentNullException("targetToAdapt"); 25 | return _innerBuilder.GetMethodInfos(targetToAdapt); 26 | } 27 | 28 | private class OpenInterfaceBuilder : IMethodInfoScanner 29 | { 30 | private readonly Type _interfaceType; 31 | 32 | public OpenInterfaceBuilder(Type interfaceType) 33 | { 34 | _interfaceType = interfaceType; 35 | } 36 | 37 | public IEnumerable GetMethodInfos(object targetToAdapt) 38 | { 39 | var interfaces = targetToAdapt.GetType().GetTypeInfo().ImplementedInterfaces; 40 | var foundItfs = (from itf in interfaces 41 | where itf.IsGenericType() && itf.GetGenericTypeDefinition() == _interfaceType 42 | select itf).ToList(); 43 | if (!foundItfs.Any()) 44 | return Enumerable.Empty(); 45 | 46 | return 47 | foundItfs 48 | .Select(itf => new ClosedInterfaceBuilder(itf)) 49 | .SelectMany(b => b.GetMethodInfos(targetToAdapt)); 50 | } 51 | } 52 | 53 | private class ClosedInterfaceBuilder : IMethodInfoScanner 54 | { 55 | private readonly Type _interfaceType; 56 | 57 | public ClosedInterfaceBuilder(Type interfaceType) 58 | { 59 | _interfaceType = interfaceType; 60 | } 61 | 62 | public IEnumerable GetMethodInfos(object targetToAdapt) 63 | { 64 | var hasInterface = targetToAdapt.GetType().GetTypeInfo().ImplementedInterfaces.Any(t => t == _interfaceType); 65 | if (!hasInterface) 66 | return Enumerable.Empty(); 67 | 68 | var runtimeInterfaceMap = targetToAdapt.GetType().GetTypeInfo().GetRuntimeInterfaceMap(_interfaceType); 69 | var candidates = runtimeInterfaceMap.InterfaceMethods.ReduceToValidMessageEndpoints(); 70 | return candidates.Select(ClassifiedMethodInfo.New); 71 | } 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /MemBus/Subscribing/Adapter/MethodBasedBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using MemBus.Support; 6 | 7 | namespace MemBus.Subscribing 8 | { 9 | internal class MethodScanner : IMethodInfoScanner 10 | { 11 | private readonly Func _methodSelector; 12 | 13 | public MethodScanner(Func methodSelector) 14 | { 15 | _methodSelector = methodSelector; 16 | } 17 | 18 | public MethodScanner(string methodName) 19 | : this(mi => mi.Name == methodName) 20 | { 21 | } 22 | 23 | public IEnumerable GetMethodInfos(object targetToAdapt) 24 | { 25 | if (targetToAdapt == null) throw new ArgumentNullException("targetToAdapt"); 26 | var candidates = targetToAdapt.GetType().MethodCandidatesForSubscriptionBuilders(_methodSelector).ToList(); 27 | return candidates.Select(ClassifiedMethodInfo.New); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /MemBus/Subscribing/DisposableSubscription.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MemBus.Support; 3 | 4 | namespace MemBus.Subscribing 5 | { 6 | public class DisposableSubscription : IDisposableSubscription, IDenyShaper, IDisposable, IKnowsSubscribedInstance 7 | { 8 | private ISubscription action; 9 | 10 | public DisposableSubscription(ISubscription action) 11 | { 12 | this.action = action; 13 | } 14 | 15 | public void Push(object message) 16 | { 17 | // To prevent a possible race condition in which the subscription is disposed and set to null 18 | // and somebody else looks up this subscription for usage, we check for null. 19 | var a = action; 20 | if (a != null) 21 | a.Push(message); 22 | } 23 | 24 | public bool Handles(Type messageType) 25 | { 26 | // To prevent a possible race condition in which the subscription is disposed and set to null 27 | // and somebody else looks up this subscription for usage, we check for null. 28 | var a = action; 29 | if (a != null) 30 | return a.Handles(messageType); 31 | return false; 32 | } 33 | 34 | public IDisposable GetDisposer() 35 | { 36 | return this; 37 | } 38 | 39 | public bool IsDisposed { get; private set; } 40 | 41 | public event EventHandler Disposed; 42 | 43 | private void raiseDispose() 44 | { 45 | IsDisposed = true; 46 | action = null; 47 | Disposed.Raise(this); 48 | } 49 | 50 | void IDisposable.Dispose() 51 | { 52 | raiseDispose(); 53 | } 54 | 55 | public bool Deny 56 | { 57 | get { return action.CheckDenyOrAllIsGood(); } 58 | } 59 | 60 | public object Instance 61 | { 62 | get { return (action as IKnowsSubscribedInstance).IfNotNull(ks => ks.Instance); } 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /MemBus/Subscribing/FilteredSubscription.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MemBus.Support; 3 | 4 | namespace MemBus.Subscribing 5 | { 6 | /// 7 | /// This subscription wraps an inner subscription where the provided filter 8 | /// is used to decide whether some given message is passed to the inner subscription or not. 9 | /// 10 | public class FilteredSubscription : ISubscription, IDenyShaper 11 | { 12 | private readonly Func _filter; 13 | private readonly ISubscription _subscription; 14 | 15 | public FilteredSubscription(Func filter, ISubscription subscription) 16 | { 17 | _filter = filter; 18 | _subscription = subscription; 19 | } 20 | 21 | public void Push(object message) 22 | { 23 | if (_filter((M) message)) 24 | _subscription.Push(message); 25 | } 26 | 27 | public bool Handles(Type messageType) 28 | { 29 | return _subscription.Handles(messageType); 30 | } 31 | 32 | 33 | public bool Deny 34 | { 35 | get { return _subscription.CheckDenyOrAllIsGood(); } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /MemBus/Subscribing/IAcceptDisposeToken.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MemBus.Subscribing 4 | { 5 | /// 6 | /// In combination with flexible subscriptions, 7 | /// when you implement this interface, the disposeToken 8 | /// will allow you to detach your instance from all 9 | /// messaging-based connections to MemBus 10 | /// 11 | public interface IAcceptDisposeToken 12 | { 13 | void Accept(IDisposable disposeToken); 14 | } 15 | } -------------------------------------------------------------------------------- /MemBus/Subscribing/IDenyShaper.cs: -------------------------------------------------------------------------------- 1 | namespace MemBus.Subscribing 2 | { 3 | /// 4 | /// Use this on a implementation to state that 5 | /// this implementation should be skipped when applying a convention driven 6 | /// 7 | public interface IDenyShaper 8 | { 9 | bool Deny { get; } 10 | } 11 | } -------------------------------------------------------------------------------- /MemBus/Subscribing/IKnowsSubscribedInstance.cs: -------------------------------------------------------------------------------- 1 | namespace MemBus.Subscribing 2 | { 3 | /// 4 | /// This may be implemented by implementors of 5 | /// If implemented, the property returns the instance which is subscribed. 6 | /// Note that this may be null if the target is a static method. 7 | /// 8 | public interface IKnowsSubscribedInstance 9 | { 10 | /// 11 | /// The instance that "belongs" to a subscription or null, if the subscription is based on a static method 12 | /// 13 | object Instance { get; } 14 | } 15 | } -------------------------------------------------------------------------------- /MemBus/Subscribing/ISubscriptionShaper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MemBus.Subscribing 4 | { 5 | /// 6 | /// This interface describes some type that typically wraps a given -instance with some new behaviour. 7 | /// Implementations readily available are: 8 | /// - 9 | /// - 10 | /// - 11 | /// - 12 | /// 13 | public interface ISubscriptionShaper 14 | { 15 | /// 16 | /// The contract to implement as subscription shaper 17 | /// 18 | ISubscription EnhanceSubscription(ISubscription subscription); 19 | } 20 | } -------------------------------------------------------------------------------- /MemBus/Subscribing/MethodInvocation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MemBus.Support; 3 | 4 | namespace MemBus.Subscribing 5 | { 6 | /// 7 | /// Basic implementation that wraps a void(Arg a) method 8 | /// as a subscription 9 | /// 10 | public class MethodInvocation : ISubscription, IKnowsSubscribedInstance 11 | { 12 | private readonly Action _action; 13 | 14 | /// 15 | /// ctor for any delegate. Can fail with 16 | /// 17 | public MethodInvocation(Delegate action) : this((Action)action) 18 | { 19 | } 20 | 21 | /// 22 | /// ctor for 23 | /// 24 | public MethodInvocation(Action action) 25 | { 26 | _action = action; 27 | } 28 | 29 | /// 30 | /// 31 | /// 32 | /// 33 | public void Push(object message) 34 | { 35 | _action((T)message); 36 | } 37 | 38 | /// 39 | /// 40 | /// 41 | public bool Handles(Type messageType) 42 | { 43 | return messageType.CanBeCastTo(); 44 | } 45 | 46 | object IKnowsSubscribedInstance.Instance 47 | { 48 | get 49 | { 50 | return _action.ExtractTarget(); 51 | } 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /MemBus/Subscribing/PublishingMethodInvocation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using MemBus.Support; 4 | 5 | namespace MemBus.Subscribing 6 | { 7 | /// 8 | /// Basic implementation that wraps a object(Arg a) method 9 | /// as a subscription, and, when invoking the method, using the return argument 10 | /// as a message to be published 11 | /// 12 | public class PublishingMethodInvocation : ISubscription, IKnowsSubscribedInstance 13 | { 14 | private readonly Func _action; 15 | private readonly IPublisher _publisher; 16 | 17 | /// 18 | /// ctor for any delegate. Can fail with 19 | /// 20 | public PublishingMethodInvocation(Delegate action, IPublisher publisher) : this((Func)action, publisher) 21 | { 22 | } 23 | 24 | /// 25 | /// ctor for and a publisher 26 | /// 27 | public PublishingMethodInvocation(Func action, IPublisher publisher) 28 | { 29 | _action = action; 30 | _publisher = publisher; 31 | } 32 | 33 | /// 34 | /// 35 | /// 36 | public void Push(object message) 37 | { 38 | var obj = _action((T)message); 39 | if (obj == null) 40 | return; 41 | var msgs = obj as IEnumerable; 42 | if (msgs != null) 43 | foreach (var msg in msgs) 44 | _publisher.Publish(msg); 45 | else 46 | _publisher.Publish(obj); 47 | } 48 | 49 | /// 50 | /// 51 | /// 52 | public bool Handles(Type messageType) 53 | { 54 | return messageType.CanBeCastTo(); 55 | } 56 | 57 | object IKnowsSubscribedInstance.Instance 58 | { 59 | get 60 | { 61 | return _action.ExtractTarget(); 62 | } 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /MemBus/Subscribing/ShapeToDispose.cs: -------------------------------------------------------------------------------- 1 | namespace MemBus.Subscribing 2 | { 3 | /// 4 | /// Use this shape to make your subscription disposable. MemBus will usually apply this shaper 5 | /// as last one in a pipeline by default. 6 | /// 7 | public class ShapeToDispose : ISubscriptionShaper 8 | { 9 | public ISubscription EnhanceSubscription(ISubscription subscription) 10 | { 11 | return subscription is IDisposableSubscription ? subscription : new DisposableSubscription(subscription); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /MemBus/Subscribing/ShapeToFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MemBus.Subscribing 4 | { 5 | 6 | /// 7 | /// Wrap a subscription such that a filter can be specified which pre-filters the messages passed to the inner subscription. 8 | /// 9 | public class ShapeToFilter : ISubscriptionShaper 10 | { 11 | private readonly Func filter; 12 | 13 | public ShapeToFilter(Func filter) 14 | { 15 | this.filter = filter; 16 | } 17 | 18 | public ISubscription EnhanceSubscription(ISubscription subscription) 19 | { 20 | return new FilteredSubscription(filter, subscription); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /MemBus/Subscribing/ShapeToPassthrough.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace MemBus.Subscribing 3 | { 4 | /// 5 | /// Application of the NullObject-Pattern to subscription shapers. 6 | /// 7 | public class ShapeToPassthrough : ISubscriptionShaper 8 | { 9 | public ISubscription EnhanceSubscription(ISubscription subscription) 10 | { 11 | return subscription; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /MemBus/Subscribing/ShapeToUIDispatch.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using MemBus.Support; 4 | 5 | namespace MemBus.Subscribing 6 | { 7 | /// 8 | /// Use this shape to specify that the enclosed subscription works on the UI thread. 9 | /// Please note that you will have to provide a TaskScheduler that will dispatch onto a given UI thread. 10 | /// 11 | public class ShapeToUiDispatch : ISubscriptionShaper, IRequireServices 12 | { 13 | private TaskScheduler _taskScheduler; 14 | 15 | public ShapeToUiDispatch(TaskScheduler taskScheduler) 16 | { 17 | this._taskScheduler = taskScheduler; 18 | } 19 | 20 | /// 21 | /// Use this constructor if the bus provides the necessary Task Scheduler for UI thread dispatching. 22 | /// 23 | public ShapeToUiDispatch() 24 | { 25 | 26 | } 27 | 28 | public ISubscription EnhanceSubscription(ISubscription subscription) 29 | { 30 | return new UiDispatchingSubscription(_taskScheduler, subscription); 31 | } 32 | 33 | void IRequireServices.AddServices(IServices svc) 34 | { 35 | _taskScheduler = _taskScheduler ?? svc.Get(); 36 | if (_taskScheduler == null) 37 | throw new InvalidOperationException("No knowledge of a UI thread is available."); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /MemBus/Subscribing/SubscriptionCustomizer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using MemBus.Support; 4 | 5 | namespace MemBus.Subscribing 6 | { 7 | /// 8 | /// You can use this in 9 | /// to allow the subscriber to customize the way it obtains messages. 10 | /// 11 | /// The message type 12 | public class SubscriptionCustomizer : ISubscriptionShaper 13 | { 14 | private readonly SubscriptionShaperAggregate _subscriptionShaperAggregate; 15 | private readonly IServices _services; 16 | 17 | private ISubscriptionShaper _filterShape; 18 | private ISubscriptionShaper _uiInvokeshape; 19 | 20 | internal SubscriptionCustomizer(SubscriptionShaperAggregate subscriptionShaperAggregate, IServices services) 21 | { 22 | _subscriptionShaperAggregate = subscriptionShaperAggregate; 23 | _services = services; 24 | } 25 | 26 | /// 27 | /// Specify a filter to be put in front of the subscription. 28 | /// 29 | public SubscriptionCustomizer SetFilter(Func filter) 30 | { 31 | _filterShape = new ShapeToFilter(filter); 32 | return this; 33 | } 34 | 35 | /// 36 | /// Say that this subscription must run on a UI thread 37 | /// 38 | public SubscriptionCustomizer DispatchOnUiThread() 39 | { 40 | if (_services.Get() == null) 41 | throw new InvalidOperationException("No knowledge of a UI thread is available. This method cannot be called. Please setup your bus for a UI scenario with RichClientFrontend"); 42 | _uiInvokeshape = new ShapeToUiDispatch(_services.Get()); 43 | return this; 44 | } 45 | 46 | ISubscription ISubscriptionShaper.EnhanceSubscription(ISubscription subscription) 47 | { 48 | _subscriptionShaperAggregate.AddNextToInner(_filterShape); 49 | _subscriptionShaperAggregate.AddNextToInner(_uiInvokeshape); 50 | return _subscriptionShaperAggregate.EnhanceSubscription(subscription); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /MemBus/Subscribing/SubscriptionPipeline.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using MemBus.Setup; 4 | using MemBus.Subscribing; 5 | using System.Linq; 6 | using MemBus.Support; 7 | 8 | namespace MemBus 9 | { 10 | internal class SubscriptionPipeline : IConfigurableSubscribing, IDisposable 11 | { 12 | private readonly IServices _services; 13 | private readonly List _shapeProviders = new List(); 14 | private SubscriptionShaperAggregate _introductionShape; 15 | 16 | public SubscriptionPipeline(IServices services) 17 | { 18 | _services = services; 19 | } 20 | 21 | /// 22 | /// Return a shaper aggregate that is applied directly to a newly introduced subscription. 23 | /// The returned instance can be further modified without affecting the infrastructure 24 | /// 25 | public SubscriptionShaperAggregate GetIntroductionShape() 26 | { 27 | return _introductionShape != null ? _introductionShape.Clone() : new SubscriptionShaperAggregate(new [] {new ShapeToPassthrough()}); 28 | } 29 | 30 | public IEnumerable Shape(IEnumerable subscriptions, object message) 31 | { 32 | var info = new MessageInfo(message); 33 | var resultingSubscriptions = subscriptions; 34 | for (int i = _shapeProviders.Count - 1; i >= 0; i--) //Backwards as we keep the default at index 0 35 | { 36 | if (!_shapeProviders[i].Handles(info)) 37 | continue; 38 | resultingSubscriptions = _shapeProviders[i].Enhance(subscriptions); 39 | break; 40 | } 41 | return resultingSubscriptions; 42 | } 43 | 44 | void IConfigurableSubscribing.DefaultShapeOutwards(params ISubscriptionShaper[] shapers) 45 | { 46 | var sp = new ShapeProvider(msg => true, _services); 47 | ((IConfigureSubscription)sp).ShapeOutwards(shapers); 48 | _shapeProviders.Insert(0, sp); 49 | } 50 | 51 | 52 | 53 | void IConfigurableSubscribing.MessageMatch(Func match, Action configure) 54 | { 55 | var sp = new ShapeProvider(match, _services); 56 | configure(sp); 57 | _shapeProviders.Add(sp); 58 | } 59 | 60 | void IConfigurableSubscribing.ApplyOnNewSubscription(params ISubscriptionShaper[] shapers) 61 | { 62 | _introductionShape = new SubscriptionShaperAggregate(shapers); 63 | } 64 | 65 | public void Dispose() 66 | { 67 | _shapeProviders.Clear(); 68 | } 69 | } 70 | 71 | internal class ShapeProvider : IConfigureSubscription, ISubscriptionShaper 72 | { 73 | private readonly Func _match; 74 | private readonly IServices _services; 75 | private readonly SubscriptionShaperAggregate _shaperAggregate = new SubscriptionShaperAggregate(); 76 | 77 | public ShapeProvider(Func match, IServices services) 78 | { 79 | if (match == null) 80 | throw new ArgumentNullException("match"); 81 | _match = match; 82 | _services = services; 83 | } 84 | 85 | public bool Handles(MessageInfo info) 86 | { 87 | return _match(info); 88 | } 89 | 90 | void IConfigureSubscription.ShapeOutwards(params ISubscriptionShaper[] shapers) 91 | { 92 | foreach (var s in shapers) 93 | { 94 | s.Being(d => d.AddServices(_services)); 95 | _shaperAggregate.Add(s); 96 | } 97 | } 98 | 99 | public IEnumerable Enhance(IEnumerable subscriptions) 100 | { 101 | return subscriptions.Select(EnhanceSubscription); 102 | } 103 | 104 | public ISubscription EnhanceSubscription(ISubscription subscription) 105 | { 106 | return _shaperAggregate.EnhanceSubscription(subscription); 107 | } 108 | } 109 | } -------------------------------------------------------------------------------- /MemBus/Subscribing/SubscriptionShaperAggregate.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace MemBus.Subscribing 6 | { 7 | 8 | /// 9 | /// Subsequent adds form a matroschka from inner to outer 10 | /// 11 | internal class SubscriptionShaperAggregate : ISubscriptionShaper, IEnumerable 12 | { 13 | readonly List _shapers = new List(); 14 | 15 | public SubscriptionShaperAggregate(IEnumerable shapers) 16 | { 17 | _shapers = new List(shapers); 18 | } 19 | 20 | public SubscriptionShaperAggregate() { } 21 | 22 | public void Add(ISubscriptionShaper shaper) 23 | { 24 | _shapers.Add(shaper); 25 | } 26 | 27 | /// 28 | /// Add a shape next to the inner one. It is allowed to pass null. In this case, nothing is added 29 | /// 30 | public void AddNextToInner(ISubscriptionShaper shaper) 31 | { 32 | if (shaper != null) 33 | addAsFirst(shaper); 34 | } 35 | 36 | public ISubscription EnhanceSubscription(ISubscription subscription) 37 | { 38 | var aggregatedSubscription = 39 | _shapers.Aggregate(subscription, (current, shaper) => shaper.EnhanceSubscription(current)); 40 | return aggregatedSubscription; 41 | } 42 | 43 | private void addAsFirst(ISubscriptionShaper shaper) 44 | { 45 | if (_shapers.Count == 0) 46 | _shapers.Add(shaper); 47 | else 48 | _shapers.Insert(0, shaper); 49 | } 50 | 51 | public SubscriptionShaperAggregate Clone() 52 | { 53 | return new SubscriptionShaperAggregate(_shapers); 54 | } 55 | 56 | public IEnumerator GetEnumerator() 57 | { 58 | return _shapers.GetEnumerator(); 59 | } 60 | 61 | IEnumerator IEnumerable.GetEnumerator() 62 | { 63 | return GetEnumerator(); 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /MemBus/Subscribing/UiDispatchingSubscription.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using MemBus.Support; 5 | 6 | namespace MemBus.Subscribing 7 | { 8 | public class UiDispatchingSubscription : ISubscription, IDenyShaper 9 | { 10 | private readonly TaskScheduler _taskScheduler; 11 | private readonly ISubscription _subscription; 12 | 13 | public UiDispatchingSubscription(TaskScheduler taskScheduler, ISubscription subscription) 14 | { 15 | _taskScheduler = taskScheduler; 16 | _subscription = subscription; 17 | } 18 | 19 | public void Push(object message) 20 | { 21 | Task.Factory.StartNew(() => 22 | _subscription.Push(message), CancellationToken.None, TaskCreationOptions.None,_taskScheduler) 23 | .Wait(); 24 | } 25 | 26 | public bool Handles(Type messageType) 27 | { 28 | return _subscription.Handles(messageType); 29 | } 30 | 31 | 32 | public bool Deny 33 | { 34 | get { return _subscription.CheckDenyOrAllIsGood(); } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /MemBus/Support/DisposeContainer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace MemBus.Support 7 | { 8 | /// 9 | /// Helps to dispose several instances at once. You can pass in any object, the container will seed 10 | /// out automatically what can be disposed and what not. 11 | /// 12 | public class DisposeContainer : IDisposable, IEnumerable 13 | { 14 | private readonly List _disposables = new List(); 15 | private readonly object _disposeLock = new object(); 16 | private volatile bool _disposed; 17 | 18 | public DisposeContainer(params object[] disposables) : this(disposables.AsEnumerable()) 19 | { 20 | } 21 | 22 | public DisposeContainer(IEnumerable disposables) 23 | { 24 | _disposables.AddRange(disposables.OfType()); 25 | } 26 | 27 | public void Add(IDisposable disposable) 28 | { 29 | _disposables.Add(disposable); 30 | } 31 | 32 | public void Add(params IDisposable[] disposables) 33 | { 34 | _disposables.AddRange(disposables); 35 | } 36 | 37 | public void Dispose() 38 | { 39 | if (_disposed) return; 40 | lock (_disposeLock) 41 | { 42 | if (_disposed) return; 43 | foreach (var d in _disposables) 44 | d.Dispose(); 45 | _disposables.Clear(); 46 | _disposed = true; 47 | } 48 | } 49 | 50 | public IEnumerator GetEnumerator() 51 | { 52 | return _disposables.GetEnumerator(); 53 | } 54 | 55 | IEnumerator IEnumerable.GetEnumerator() 56 | { 57 | return GetEnumerator(); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /MemBus/Support/IRequireServices.cs: -------------------------------------------------------------------------------- 1 | using MemBus.Publishing; 2 | using MemBus.Subscribing; 3 | 4 | namespace MemBus.Support 5 | { 6 | /// 7 | /// Specify that you want access to the instance of MemBus. 8 | /// You can use this on and instances. 9 | /// 10 | public interface IRequireServices 11 | { 12 | void AddServices(IServices svc); 13 | } 14 | 15 | /// 16 | /// Specify that you want access to the instance of MemBus. 17 | /// You can use this on instances. 18 | /// 19 | public interface IRequireBus 20 | { 21 | void AddBus(IBus bus); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /MemBus/Support/IServices.cs: -------------------------------------------------------------------------------- 1 | namespace MemBus.Support 2 | { 3 | public interface IServices 4 | { 5 | void Add(T @object); 6 | void Remove(); 7 | T Get(); 8 | /// 9 | /// Gives you a summary of contained services 10 | /// 11 | string WhatDoIHave { get; } 12 | } 13 | 14 | public interface IServices : IServices 15 | { 16 | void AddExtension(T extension) where T : IServicesExtension; 17 | void RemoveExtension() where T : IServicesExtension; 18 | TARGET Replace(T @object); 19 | TARGET CloneContext(); 20 | } 21 | 22 | public interface IServicesExtension 23 | { 24 | void Attach(T target); 25 | void Remove(T target); 26 | } 27 | } -------------------------------------------------------------------------------- /MemBus/Support/MemBusException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MemBus.Support 4 | { 5 | /// 6 | /// Message to denote an exception caused by Membus 7 | /// 8 | public class MemBusException : Exception 9 | { 10 | /// 11 | /// ctor 12 | /// 13 | public MemBusException(string message) : base(message) 14 | { 15 | } 16 | 17 | /// 18 | /// ctor 19 | /// 20 | public MemBusException(string message, Exception inner) : base(message, inner) 21 | { 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /MemBus/Support/Publish.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MemBus.Publishing; 3 | 4 | namespace MemBus.Support 5 | { 6 | public class Publish 7 | { 8 | /// 9 | /// Provides a publish pipeline member that will publish a provided message 10 | /// 11 | public static IPublishPipelineMember This(object message) 12 | { 13 | return new PipelineMemberToPublishAMessage(message); 14 | } 15 | 16 | private class PipelineMemberToPublishAMessage : IPublishPipelineMember, IRequireBus 17 | { 18 | private readonly object _message; 19 | private IBus _bus; 20 | 21 | public PipelineMemberToPublishAMessage(object message) 22 | { 23 | _message = message; 24 | } 25 | 26 | public void LookAt(PublishToken token) 27 | { 28 | _bus.Publish(_message); 29 | } 30 | 31 | void IRequireBus.AddBus(IBus bus) 32 | { 33 | _bus = bus; 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /MemBus/Support/ReflectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Linq; 4 | 5 | namespace MemBus.Support 6 | { 7 | /// 8 | /// A number of reflection Extension Methods to help you 9 | /// in working with attributes and other reflection mechanisms 10 | /// 11 | public static class ReflectionExtensions 12 | { 13 | 14 | /// 15 | /// Determine whether a certain custom attribute is specified on this element 16 | /// 17 | /// The type of the attribute you are looking for 18 | /// Target 19 | /// True if this attribute is defined on this target 20 | public static bool HasAttribute(this MemberInfo provider) where T : Attribute 21 | { 22 | return provider.GetCustomAttributes().Any(); 23 | } 24 | 25 | /// 26 | /// Normalization method 27 | /// 28 | public static bool IsGenericType(this Type type) 29 | { 30 | return type.GetTypeInfo().IsGenericType; 31 | } 32 | 33 | public static InterfaceMapping GetRuntimeInterfaceMap(this Type type, Type interfaceType) 34 | { 35 | return type.GetTypeInfo().GetRuntimeInterfaceMap(interfaceType); 36 | } 37 | 38 | public static bool IsConcreteObservable(this Type type) 39 | { 40 | return type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof (IObservable<>); 41 | } 42 | 43 | /// 44 | /// Get a specific attribute from your target 45 | /// 46 | /// The type of the attribute you are looking for 47 | /// Target 48 | /// The first attribute on the target that is of the desired type 49 | public static T GetAttribute(this MemberInfo provider) where T : Attribute 50 | { 51 | return provider.GetCustomAttributes().FirstOrDefault(); 52 | } 53 | 54 | /// 55 | /// Ask some class whether it implements a certain interface 56 | /// 57 | /// The interface you are looking for 58 | /// the inspected type 59 | /// true if the interface is imaplemented 60 | public static bool ImplementsInterface(this Type type) where T : class 61 | { 62 | return type.GetTypeInfo().ImplementedInterfaces.Any(t => t == typeof(T)); 63 | } 64 | 65 | public static bool CanBeCastTo(this Type type) 66 | { 67 | return typeof (T).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()); 68 | } 69 | 70 | public static bool CanBeCastTo(this Type type, Type otherType) 71 | { 72 | return otherType.GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()); 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /MemBus/Support/SubscriptionCandidatesExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using MemBus.Subscribing; 6 | 7 | namespace MemBus.Support 8 | { 9 | public static class SubscriptionCandidatesExtensions 10 | { 11 | public static bool InterfaceIsSuitableAsIoCHandler(this Type itf) 12 | { 13 | var methods = itf.GetRuntimeMethods().ToList(); 14 | return methods.Count == 1 && 15 | methods[0].GetParameters().Length == 1 && 16 | methods[0].ReturnType == typeof (void); 17 | } 18 | 19 | public static IEnumerable MethodCandidatesForSubscriptionBuilders(this Type reflectedType,Func methodSelector) 20 | { 21 | var disposeTokenMethod = reflectedType.ImplementsInterface() 22 | ? (mi => mi.Name == "Accept" && mi.GetParameters().Length == 1 && 23 | mi.GetParameters()[0].ParameterType == typeof(IDisposable)) 24 | : new Func(mi => false); 25 | 26 | return reflectedType.GetRuntimeMethods().ReduceToValidMessageEndpoints( 27 | mi => mi.DeclaringType == reflectedType && methodSelector(mi), 28 | disposeTokenMethod); 29 | } 30 | 31 | /// 32 | /// Picks out those methods that MemBus can use as targets/sources of message-passing tasks 33 | /// 34 | public static IEnumerable ReduceToValidMessageEndpoints( 35 | this IEnumerable methods, 36 | Func additionalMethodSelector = null, 37 | Func additionalMethodExclusion = null) 38 | { 39 | additionalMethodSelector = additionalMethodSelector ?? (info => true); 40 | additionalMethodExclusion = additionalMethodExclusion ?? (info => false); 41 | 42 | return 43 | from mi in methods 44 | where 45 | !mi.IsGenericMethod && 46 | !mi.IsStatic && 47 | mi.IsPublic && 48 | mi.HasOneParameterOrNoneAndReturnsIObservable() && 49 | !additionalMethodExclusion(mi) && 50 | additionalMethodSelector(mi) 51 | select mi; 52 | } 53 | 54 | private static bool HasOneParameterOrNoneAndReturnsIObservable(this MethodInfo mi) 55 | { 56 | return 57 | mi.GetParameters().Length == 1 || 58 | ( 59 | mi.GetParameters().Length == 0 && 60 | mi.ReturnType.IsConstructedGenericType && 61 | mi.ReturnType.GetGenericTypeDefinition() == typeof(IObservable<>) 62 | ); 63 | } 64 | } 65 | 66 | 67 | } -------------------------------------------------------------------------------- /MemBus/Support/UsefulExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Threading.Tasks; 6 | using MemBus.Messages; 7 | using MemBus.Subscribing; 8 | 9 | namespace MemBus.Support 10 | { 11 | /// 12 | /// Extensions used internally by Membus 13 | /// 14 | public static class UsefulExtensions 15 | { 16 | /// 17 | /// Sends off messages on a bus based on a list of faulted tasks 18 | /// 19 | public static void PublishExceptionMessages(this Task[] tasks, IBus bus) 20 | { 21 | foreach (var t in tasks.Where(t => t.IsFaulted)) 22 | bus.Publish(new ExceptionOccurred(t.Exception)); 23 | } 24 | 25 | /// 26 | /// Raise an event 27 | /// 28 | public static void Raise(this EventHandler @event, object sender) 29 | { 30 | if (@event != null) 31 | @event(sender, EventArgs.Empty); 32 | } 33 | 34 | /// 35 | /// A single item as Enumerable 36 | /// 37 | public static IEnumerable AsEnumerable(this T item) 38 | { 39 | return new[] {item}; 40 | } 41 | 42 | /// 43 | /// Perform an action with every item of an enumerable 44 | /// 45 | public static void Each(this IEnumerable items, Action action) 46 | { 47 | foreach (var i in items) 48 | action(i); 49 | } 50 | 51 | /// 52 | /// string.Format as extension method 53 | /// 54 | public static string Fmt(this string @string, params object[] args) 55 | { 56 | if (@string == null) throw new ArgumentNullException("string"); 57 | return string.Format(@string, args); 58 | } 59 | 60 | /// 61 | /// Perform an action if o is also of type T 62 | /// 63 | public static void Being(this object o, Action action) where T : class 64 | { 65 | var t = o as T; 66 | if (t != null) 67 | action(t); 68 | } 69 | 70 | /// 71 | /// Return the value accessesd by selector or the default(O) if the input is null 72 | /// 73 | public static O IfNotNull(this I input, Func selector) where I : class 74 | { 75 | return input != null ? selector(input) : default(O); 76 | } 77 | 78 | internal static bool CheckDenyOrAllIsGood(this object obj) 79 | { 80 | return obj is IDenyShaper && ((IDenyShaper)obj).Deny; 81 | } 82 | 83 | internal static IDisposable TryReturnDisposerOfSubscription(this ISubscription sub) 84 | { 85 | return sub is IDisposableSubscription ? ((IDisposableSubscription)sub).GetDisposer() : null; 86 | } 87 | 88 | public static object ExtractTarget(this Delegate action) 89 | { 90 | if (action.Target == null) 91 | return null; 92 | // Disgusting fact: The Closure Type is not public 93 | if (action.Target.GetType().Name == "Closure") 94 | { 95 | var rF = action.Target.GetType().GetRuntimeField("Constants"); 96 | var constants = (object[])rF.GetValue(action.Target); 97 | return constants[0]; 98 | } 99 | return action.Target; 100 | } 101 | 102 | 103 | 104 | } 105 | } --------------------------------------------------------------------------------