├── .gitignore ├── LICENSE ├── README.md ├── readme-img ├── example-contracts-project.png └── example-contracts.png └── src ├── BlazorComponentBus.Extensions.DependencyInjection ├── BlazorComponentBus.Extensions.DependencyInjection.csproj └── ServiceCollectionExtensions.cs ├── BlazorComponentBus.UnitTests ├── BlazorComponentBus.UnitTests.csproj ├── ComponentBusTests.cs └── DependencyInjectionTests.cs ├── BlazorComponentBus.sln └── BlazorComponentBus ├── BlazorComponentBus.csproj ├── ComponentBus.cs ├── IComponentBus.cs ├── MessageArgs.cs └── Subscribing └── ComponentCallBack.cs /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.*~ 3 | project.lock.json 4 | .DS_Store 5 | *.pyc 6 | nupkg/ 7 | 8 | # Visual Studio Code 9 | .vscode 10 | 11 | # Rider 12 | .idea 13 | 14 | # User-specific files 15 | *.suo 16 | *.user 17 | *.userosscache 18 | *.sln.docstates 19 | 20 | # Build results 21 | [Dd]ebug/ 22 | [Dd]ebugPublic/ 23 | [Rr]elease/ 24 | [Rr]eleases/ 25 | x64/ 26 | x86/ 27 | build/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Oo]ut/ 32 | msbuild.log 33 | msbuild.err 34 | msbuild.wrn 35 | 36 | # Visual Studio 2015 37 | .vs/ 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Colin Pear 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BlazorComponentBus 2 | Enables loosely coupled messaging between Blazor UI Components. Yes! Thats right! UI messaging with Blazor. 3 | 4 | ## Whats New in v2.2.0 5 | - Updates API to include additional options for for subscribe and unsubscribe 6 | 7 | ## Full Working Example 8 | For a full working example of using Blazor Component Bus (with source code) check out this Article on [Blazor UI Composition](https://clearmeasure.com/blazor-ui-composition/). 9 | 10 | ## Install BlazorComponentBus 11 | 12 | Install the [BlazorComponentBus](https://www.nuget.org/packages/BlazorComponentBus) nuget package to your Blazor project. 13 | 14 | Install-Package BlazorComponentBus 15 | 16 | Or via the .NET Core command line interface: 17 | 18 | dotnet add package BlazorComponentBus 19 | 20 | Either commands, from Package Manager Console or .NET Core CLI, will download and install Blazor Component Bus. 21 | 22 | ## Setup 23 | In your .NET Core app add the following line to make the ComponentBus available for use in your code. 24 | 25 | public void ConfigureServices(IServiceCollection services) 26 | { 27 | ... 28 | services.AddScoped(); 29 | } 30 | 31 | Next you will need to create message types. These are simple class files that can contain properties for passing information along to and from each component. The classes/objects are the messages that will be sent. The messages are the contracts that can be referenced by the components. The best practice is to create a contracts or messages folder for containg messages your component will emit. 32 | 33 | ![Screenshot](readme-img/example-contracts.png) 34 | 35 | An alternative approach would be to put your components and message contracts into separate projects. I prefer this approach especially if your components are in their own projects. 36 | 37 | ![Screenshot](readme-img/example-contracts-project.png) 38 | 39 | An example message might look like this: 40 | 41 | public class StudentAddedEvent 42 | { 43 | public Guid StudentId { get; set; } 44 | } 45 | 46 | ## Subscribe to a message type 47 | 48 | Blazor Components you create can react to events that are submitted by subscribing to them. In the example above the **SchoollRosterComponent** would subscribe to the **StudentAddedEvent**. 49 | 50 | To subscribe to an event/message you must call Bus.Subscribe(). This will let the message broker know what type of message your blazor component is subscribed to and what to do when it gets one (execute a callback to the subscribing component). Pay attention to _var message = args.GetMessage();_. This is how you access data from the message itself. 51 | 52 | @inject BlazorComponentBus.ComponentBus Bus 53 | 54 |
Body of the SchoolRosterComponent
55 | 56 | @code 57 | { 58 | protected override void OnInitialized() 59 | { 60 | //Subscribe Component to Specific Message 61 | Bus.Subscribe(StudentAddedHandler); 62 | } 63 | 64 | private void StudentAddedHandler(MessageArgs args) 65 | { 66 | var message = args.GetMessage(); 67 | 68 | var id = message.StudentId; 69 | 70 | ...Do Something 71 | } 72 | 73 | } 74 | 75 | ## Publish a message 76 | 77 | To publish a message you must call the Bus.Publish() method and pass in the message you want to send. In the example above the **StudentComponent** might publish a **StudentAddedEvent** when a new student form was filled out. 78 | 79 | @inject BlazorComponentBus.ComponentBus Bus 80 | 81 |

StudentComponent

82 | 83 | @code { 84 | 85 | public void SendItOutThere() 86 | { 87 | Bus.Publish(new StudentAddedEvent{StudentId = new Guid()}); 88 | } 89 | } 90 | 91 | 92 | ## Async Callback Example 93 | 94 | 95 | @inject BlazorComponentBus.IComponentBus Bus 96 |
My Component
97 | 98 | @code 99 | { 100 | protected override async Task OnInitializedAsync() 101 | { 102 | Bus.Subscribe(DoSomething); 103 | Bus.Subscribe(DoSomethingAsync); 104 | } 105 | 106 | private void DoSomething(MessageArgs args) 107 | { 108 | // do something 109 | } 110 | 111 | private async Task DoSomethingAsync(MessageArgs args, CancellationToken ct) 112 | { 113 | // await something 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /readme-img/example-contracts-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpear/BlazorComponentBus/8cc88f7300db93a02b9393d8deadc954dd8c90d8/readme-img/example-contracts-project.png -------------------------------------------------------------------------------- /readme-img/example-contracts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpear/BlazorComponentBus/8cc88f7300db93a02b9393d8deadc954dd8c90d8/readme-img/example-contracts.png -------------------------------------------------------------------------------- /src/BlazorComponentBus.Extensions.DependencyInjection/BlazorComponentBus.Extensions.DependencyInjection.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/BlazorComponentBus.Extensions.DependencyInjection/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace BlazorComponentBus.Extensions.DependencyInjection; 4 | 5 | public static class ServiceCollectionExtensions 6 | { 7 | 8 | public static IServiceCollection AddBlazorComponentBus(this IServiceCollection services) 9 | { 10 | services.AddScoped(); 11 | services.AddScoped(); 12 | return services; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/BlazorComponentBus.UnitTests/BlazorComponentBus.UnitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | all 17 | 18 | 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | all 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/BlazorComponentBus.UnitTests/ComponentBusTests.cs: -------------------------------------------------------------------------------- 1 | using BlazorComponentBus.Extensions; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Xunit; 5 | 6 | namespace BlazorComponentBus.UnitTests 7 | { 8 | public class ComponentBusTests 9 | { 10 | 11 | [Fact] 12 | public async Task ShouldSubscribeToAndPublishAnEvent() 13 | { 14 | var bus = new ComponentBus(); 15 | var publisher = new PublishingComponent(bus); 16 | var subscriber = new SubscribingComponent(); 17 | 18 | subscriber.SubscribeThisComponent(bus); 19 | 20 | await publisher.PublishTestMessageEvent(); 21 | 22 | Assert.Equal(1, subscriber.Count); 23 | } 24 | 25 | [Fact] 26 | public async Task ShouldUnsubscribe() 27 | { 28 | var bus = new ComponentBus(); 29 | var publisher = new PublishingComponent(bus); 30 | var subscriber = new SubscribingComponent(); 31 | 32 | subscriber.SubscribeThisComponent(bus); 33 | 34 | subscriber.UnsubscribeThisComponent(bus); 35 | 36 | await publisher.PublishTestMessageEvent(); 37 | 38 | Assert.Equal(0, subscriber.Count); 39 | } 40 | 41 | [Fact] 42 | public void ShouldNotFailIfNoSubscriberFoundWhenUnsubscribing() 43 | { 44 | var bus = new ComponentBus(); 45 | var subscriber = new SubscribingComponent(); 46 | 47 | //There is no subscriber but we are going to try an unsubscribe anyway 48 | subscriber.UnsubscribeThisComponent(bus); 49 | 50 | Assert.True(true); 51 | } 52 | 53 | [Fact] 54 | public async Task ShouldNotFailIfPublishWithNoSubscribers() 55 | { 56 | var bus = new ComponentBus(); 57 | var publisher = new PublishingComponent(bus); 58 | 59 | //There is no subscriber but we are going to try and publish an event 60 | await publisher.PublishTestMessageEvent(); 61 | 62 | Assert.True(true); 63 | } 64 | 65 | [Fact] 66 | public async Task ShouldOnlyUnsubscribeOneComponentAndNotAll() 67 | { 68 | var bus = new ComponentBus(); 69 | var publisher = new PublishingComponent(bus); 70 | var subscriber = new SubscribingComponent(); //Different Type 71 | var secondSubscriber = new SecondSubscribingComponent(); //Different Type 72 | 73 | subscriber.SubscribeThisComponent(bus); 74 | secondSubscriber.SubscribeThisComponent(bus); 75 | 76 | subscriber.UnsubscribeThisComponent(bus); 77 | 78 | await publisher.PublishTestMessageEvent(); 79 | 80 | Assert.Equal(0, subscriber.Count); 81 | Assert.Equal(1, secondSubscriber.Count); 82 | } 83 | 84 | [Fact] 85 | public async Task ShouldOnlyUnsubscribeOneComponentAndNotAllMultiInstance() 86 | { 87 | var bus = new ComponentBus(); 88 | var publisher = new PublishingComponent(bus); 89 | var subscriber = new SubscribingComponent(); //Same Type 90 | var secondSubscriber = new SubscribingComponent(); //Same Type 91 | 92 | subscriber.SubscribeThisComponent(bus); 93 | secondSubscriber.SubscribeThisComponent(bus); 94 | 95 | subscriber.UnsubscribeThisComponent(bus); 96 | 97 | await publisher.PublishTestMessageEvent(); 98 | 99 | Assert.Equal(0, subscriber.Count); 100 | Assert.Equal(1, secondSubscriber.Count); 101 | } 102 | 103 | [Fact] 104 | public async Task ShouldOnlyUnsubscribeOneEventType() 105 | { 106 | var bus = new ComponentBus(); 107 | var publisher = new PublishingComponent(bus); 108 | var subscriber = new SubscribingComponent(); 109 | 110 | subscriber.SubscribeThisComponent(bus); 111 | subscriber.SubscribeThisComponent(bus); 112 | 113 | subscriber.UnsubscribeThisComponent(bus); 114 | 115 | await publisher.PublishTestMessageEvent(); 116 | await publisher.PublishAnotherTestEventMessage(); 117 | 118 | Assert.Equal(1, subscriber.Count); 119 | } 120 | 121 | [Fact] 122 | public async Task ShouldSubscribeTo2Events() 123 | { 124 | var bus = new ComponentBus(); 125 | var publisher = new PublishingComponent(bus); 126 | var subscriber = new SubscribingComponent(); 127 | 128 | subscriber.SubscribeThisComponent(bus); 129 | subscriber.SubscribeThisComponent(bus); 130 | 131 | 132 | await publisher.PublishTestMessageEvent(); 133 | await publisher.PublishAnotherTestEventMessage(); 134 | 135 | Assert.Equal(2, subscriber.Count); 136 | } 137 | 138 | [Fact] 139 | public async Task MultiSubscribeToSameMessageShouldOnlyGetOneResult() 140 | { 141 | var bus = new ComponentBus(); 142 | var publisher = new PublishingComponent(bus); 143 | var subscriber = new SubscribingComponent(); 144 | 145 | subscriber.SubscribeThisComponent(bus); 146 | subscriber.SubscribeThisComponent(bus); 147 | subscriber.SubscribeThisComponent(bus); 148 | subscriber.SubscribeThisComponent(bus); 149 | subscriber.SubscribeThisComponent(bus); 150 | subscriber.SubscribeThisComponent(bus); 151 | subscriber.SubscribeThisComponent(bus); 152 | subscriber.SubscribeThisComponent(bus); 153 | 154 | await publisher.PublishTestMessageEvent(); 155 | 156 | Assert.Equal(1, subscriber.Count); 157 | } 158 | 159 | [Fact] 160 | public async Task AsyncSubscribeShouldSubscribeToAndPublishAnEvent() 161 | { 162 | var bus = new ComponentBus(); 163 | var publisher = new PublishingComponent(bus); 164 | var subscriber = new AsyncSubscribingComponent(); 165 | 166 | subscriber.SubscribeThisComponent(bus); 167 | 168 | await publisher.PublishTestMessageEvent(); 169 | 170 | Assert.Equal(1, subscriber.Count); 171 | } 172 | 173 | [Fact] 174 | public async Task AsyncSubscribeShouldUnsubscribe() 175 | { 176 | var bus = new ComponentBus(); 177 | var publisher = new PublishingComponent(bus); 178 | var subscriber = new AsyncSubscribingComponent(); 179 | 180 | subscriber.SubscribeThisComponent(bus); 181 | subscriber.UnsubscribeThisComponent(bus); 182 | 183 | await publisher.PublishTestMessageEvent(); 184 | 185 | Assert.Equal(0, subscriber.Count); 186 | } 187 | 188 | [Fact] 189 | public async Task AsyncSubscribeMultiSubscribeToSameMessageShouldOnlyGetOneResult() 190 | { 191 | var bus = new ComponentBus(); 192 | var publisher = new PublishingComponent(bus); 193 | var subscriber = new AsyncSubscribingComponent(); 194 | 195 | subscriber.SubscribeThisComponent(bus); 196 | subscriber.SubscribeThisComponent(bus); 197 | subscriber.SubscribeThisComponent(bus); 198 | subscriber.SubscribeThisComponent(bus); 199 | subscriber.SubscribeThisComponent(bus); 200 | subscriber.SubscribeThisComponent(bus); 201 | subscriber.SubscribeThisComponent(bus); 202 | subscriber.SubscribeThisComponent(bus); 203 | 204 | await publisher.PublishTestMessageEvent(); 205 | 206 | Assert.Equal(1, subscriber.Count); 207 | } 208 | 209 | 210 | [Fact] 211 | public async Task ShouldSubscribeWithExtensionMethod() 212 | { 213 | var bus = new ComponentBus(); 214 | var publisher = new PublishingComponent(bus); 215 | var subscriber = new SubscribingComponent(); 216 | 217 | subscriber.SubscribeToThisComponent(bus); 218 | 219 | await publisher.PublishTestMessageEvent(); 220 | 221 | Assert.Equal(1, subscriber.Count); 222 | } 223 | 224 | [Fact] 225 | public async Task ShouldSubscribeAsyncWithExtensionMethod() 226 | { 227 | 228 | var bus = new ComponentBus(); 229 | var publisher = new PublishingComponent(bus); 230 | var subscriber = new AsyncSubscribingComponent(); 231 | 232 | subscriber.SubscribeToThisComponent(bus); 233 | 234 | await publisher.PublishTestMessageEvent(); 235 | 236 | Assert.Equal(1, subscriber.Count); 237 | } 238 | 239 | 240 | [Fact] 241 | public async Task ShouldUnSubscribeWithExtensionMethod() 242 | { 243 | var bus = new ComponentBus(); 244 | var publisher = new PublishingComponent(bus); 245 | var subscriber = new SubscribingComponent(); 246 | 247 | subscriber.SubscribeToThisComponent(bus); 248 | subscriber.UnsubscribeFromThisComponent(bus); 249 | 250 | await publisher.PublishTestMessageEvent(); 251 | 252 | Assert.Equal(0, subscriber.Count); 253 | } 254 | 255 | [Fact] 256 | public async Task ShouldUnSubscribeAsyncWithExtensionMethod() 257 | { 258 | 259 | var bus = new ComponentBus(); 260 | var publisher = new PublishingComponent(bus); 261 | var subscriber = new AsyncSubscribingComponent(); 262 | 263 | subscriber.SubscribeToThisComponent(bus); 264 | subscriber.UnsubscribeFromThisComponent(bus); 265 | 266 | await publisher.PublishTestMessageEvent(); 267 | 268 | Assert.Equal(0, subscriber.Count); 269 | } 270 | 271 | 272 | } 273 | 274 | 275 | 276 | 277 | 278 | public class PublishingComponent 279 | { 280 | private readonly IComponentBus _bus; 281 | 282 | public PublishingComponent(IComponentBus bus) => _bus = bus; 283 | 284 | public Task PublishTestMessageEvent() => _bus.Publish(new TestEventMessage()); 285 | public Task PublishAnotherTestEventMessage() => _bus.Publish(new AnotherTestEventMessage()); 286 | } 287 | 288 | public class SubscribingComponent 289 | { 290 | public int Count { get; set; } 291 | 292 | public void SubscribeThisComponent(IComponentBus bus) => bus.Subscribe(ReceiveMessage); 293 | public void SubscribeToThisComponent(IComponentBus bus) => bus.SubscribeTo(ReceiveMessage); 294 | public void UnsubscribeThisComponent(IComponentBus bus) => bus.UnSubscribe(ReceiveMessage); 295 | public void UnsubscribeFromThisComponent(IComponentBus bus) => bus.UnSubscribeFrom(ReceiveMessage); 296 | 297 | public void ReceiveMessage(MessageArgs args) => Count++; 298 | public void ReceiveMessage(T message) => Count++; 299 | 300 | } 301 | 302 | public class AsyncSubscribingComponent 303 | { 304 | public int Count { get; set; } 305 | 306 | public void SubscribeThisComponent(ComponentBus bus) => bus.Subscribe(ReceiveMessageAsync); 307 | public void SubscribeToThisComponent(IComponentBus bus) => bus.SubscribeTo(ReceiveMessageAsync); 308 | public void UnsubscribeThisComponent(ComponentBus bus) => bus.UnSubscribe(ReceiveMessageAsync); 309 | public void UnsubscribeFromThisComponent(IComponentBus bus) => bus.UnSubscribeFrom(ReceiveMessageAsync); 310 | 311 | public Task ReceiveMessageAsync(MessageArgs args, CancellationToken ct) 312 | { 313 | Count++; 314 | return Task.CompletedTask; 315 | } 316 | 317 | public Task ReceiveMessageAsync(T message, CancellationToken ct) 318 | { 319 | Count++; 320 | return Task.CompletedTask; 321 | } 322 | } 323 | 324 | public class SecondSubscribingComponent 325 | { 326 | public int Count { get; set; } 327 | 328 | public void SubscribeThisComponent(ComponentBus bus) => bus.Subscribe(ReceiveMessage); 329 | public void UnsubscribeThisComponent(ComponentBus bus) => bus.UnSubscribe(ReceiveMessage); 330 | public void ReceiveMessage(MessageArgs args) => Count++; 331 | } 332 | 333 | public sealed record TestEventMessage(); 334 | public sealed record AnotherTestEventMessage(); 335 | } 336 | -------------------------------------------------------------------------------- /src/BlazorComponentBus.UnitTests/DependencyInjectionTests.cs: -------------------------------------------------------------------------------- 1 | using BlazorComponentBus.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Xunit; 4 | 5 | namespace BlazorComponentBus.UnitTests; 6 | 7 | 8 | public sealed class DependencyInjectionTests 9 | { 10 | 11 | [Fact] 12 | public void ServiceProviderShouldInjectIComponentBus() 13 | { 14 | using var serviceProvider = new ServiceCollection().AddBlazorComponentBus().BuildServiceProvider(); 15 | Assert.NotNull(serviceProvider.GetService()); 16 | } 17 | 18 | [Fact] 19 | public void ServiceProviderShouldInjectComponentBus() 20 | { 21 | using var serviceProvider = new ServiceCollection().AddBlazorComponentBus().BuildServiceProvider(); 22 | Assert.NotNull(serviceProvider.GetService()); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/BlazorComponentBus.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.32112.339 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorComponentBus", "BlazorComponentBus\BlazorComponentBus.csproj", "{9C12BC91-5860-4DE5-9F00-D4CE67E85A53}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorComponentBus.UnitTests", "BlazorComponentBus.UnitTests\BlazorComponentBus.UnitTests.csproj", "{7BC69FB0-6486-4E1A-A080-C7DD2B01EF8E}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorComponentBus.Extensions.DependencyInjection", "BlazorComponentBus.Extensions.DependencyInjection\BlazorComponentBus.Extensions.DependencyInjection.csproj", "{536D7AE4-6B77-4EE1-8D9B-10EDD3BBEECF}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {9C12BC91-5860-4DE5-9F00-D4CE67E85A53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {9C12BC91-5860-4DE5-9F00-D4CE67E85A53}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {9C12BC91-5860-4DE5-9F00-D4CE67E85A53}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {9C12BC91-5860-4DE5-9F00-D4CE67E85A53}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {7BC69FB0-6486-4E1A-A080-C7DD2B01EF8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {7BC69FB0-6486-4E1A-A080-C7DD2B01EF8E}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {7BC69FB0-6486-4E1A-A080-C7DD2B01EF8E}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {7BC69FB0-6486-4E1A-A080-C7DD2B01EF8E}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {536D7AE4-6B77-4EE1-8D9B-10EDD3BBEECF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {536D7AE4-6B77-4EE1-8D9B-10EDD3BBEECF}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {536D7AE4-6B77-4EE1-8D9B-10EDD3BBEECF}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {536D7AE4-6B77-4EE1-8D9B-10EDD3BBEECF}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {09BDF981-1E14-4ED8-AC17-CA7049C02026} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /src/BlazorComponentBus/BlazorComponentBus.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 2.1.0 8 | BlazorComponentBus 9 | Colin Pear 10 | Blazor Component Bus enables component to component communication using messaging and the pub/sub pattern. 11 | https://clearmeasure.com/blazor-ui-composition/ 12 | https://github.com/cpear/BlazorComponentBus 13 | Colin Pear 14 | https://github.com/cpear/BlazorComponentBus/blob/master/LICENSE 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/BlazorComponentBus/ComponentBus.cs: -------------------------------------------------------------------------------- 1 | using BlazorComponentBus.Subscribing; 2 | 3 | namespace BlazorComponentBus; 4 | 5 | public class ComponentBus : IComponentBus 6 | { 7 | private readonly List> _registeredComponents = new(); 8 | 9 | 10 | public void Subscribe(ComponentCallBack componentCallback) => 11 | Subscribe(componentCallback as object); 12 | 13 | public void SubscribeTo(ComponentCallBack componentCallback) => 14 | Subscribe(componentCallback); 15 | 16 | public void Subscribe(AsyncComponentCallBack componentCallback) => 17 | Subscribe(componentCallback as object); 18 | 19 | public void SubscribeTo(AsyncComponentCallBack componentCallback) => 20 | Subscribe(componentCallback); 21 | 22 | private void Subscribe(object componentCallback) 23 | { 24 | /* Unsubscribe first just in case the same component subscribes to the same callback twice. 25 | This Prevents multiple callbacks to the same component. */ 26 | UnSubscribe(componentCallback); 27 | 28 | _registeredComponents.Add(new KeyValuePair(typeof(T), componentCallback)); 29 | } 30 | 31 | public void UnSubscribe(ComponentCallBack componentCallBack) => 32 | UnSubscribe(componentCallBack as object); 33 | 34 | public void UnSubscribeFrom(ComponentCallBack componentCallback) => 35 | UnSubscribe(componentCallback); 36 | 37 | public void UnSubscribe(AsyncComponentCallBack componentCallBack) => 38 | UnSubscribe(componentCallBack as object); 39 | 40 | public void UnSubscribeFrom(AsyncComponentCallBack componentCallback) => 41 | UnSubscribe(componentCallback); 42 | 43 | private void UnSubscribe(object componentCallBack) 44 | { 45 | _registeredComponents.Remove(new KeyValuePair(typeof(T), componentCallBack)); 46 | } 47 | 48 | public Task Publish(T message) => Publish(message, CancellationToken.None); 49 | 50 | public async Task Publish(T message, CancellationToken ct) 51 | { 52 | var messageType = typeof(T); 53 | 54 | var args = new MessageArgs(message!); 55 | 56 | var subscribers = _registeredComponents.ToLookup(item => item.Key); 57 | 58 | //Look for subscribers of this message type 59 | //Call the subscriber and pass the message along 60 | foreach (var subscriber in subscribers[messageType].Select(_ => _.Value)) 61 | { 62 | if (subscriber is ComponentCallBack syncCallback) 63 | { 64 | await Task.Run(() => syncCallback.Invoke(args), ct); 65 | } 66 | else if (subscriber is ComponentCallBack genericSyncCallback) 67 | { 68 | await Task.Run(() => genericSyncCallback.Invoke(message)); 69 | } 70 | else if (subscriber is AsyncComponentCallBack asyncCallback) 71 | { 72 | await Task.Run(() => asyncCallback.Invoke(args, ct), ct); 73 | } 74 | else if (subscriber is AsyncComponentCallBack genericAsyncCallback) 75 | { 76 | await Task.Run(() => genericAsyncCallback.Invoke(message, ct), ct); 77 | } 78 | } 79 | 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/BlazorComponentBus/IComponentBus.cs: -------------------------------------------------------------------------------- 1 | using BlazorComponentBus.Subscribing; 2 | 3 | namespace BlazorComponentBus; 4 | 5 | public interface IComponentBus 6 | { 7 | Task Publish(T message); 8 | Task Publish(T message, CancellationToken ct); 9 | void Subscribe(AsyncComponentCallBack componentCallback); 10 | void Subscribe(ComponentCallBack componentCallback); 11 | void SubscribeTo(AsyncComponentCallBack componentCallback); 12 | void SubscribeTo(ComponentCallBack componentCallback); 13 | void UnSubscribe(AsyncComponentCallBack componentCallBack); 14 | void UnSubscribe(ComponentCallBack componentCallBack); 15 | void UnSubscribeFrom(AsyncComponentCallBack componentCallback); 16 | void UnSubscribeFrom(ComponentCallBack componentCallback); 17 | } 18 | -------------------------------------------------------------------------------- /src/BlazorComponentBus/MessageArgs.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorComponentBus 2 | { 3 | public class MessageArgs : EventArgs 4 | { 5 | private readonly object _message; 6 | 7 | public MessageArgs(object message) 8 | { 9 | _message = message; 10 | } 11 | 12 | public TypeIExpect GetMessage() 13 | { 14 | return (TypeIExpect)_message; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/BlazorComponentBus/Subscribing/ComponentCallBack.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorComponentBus.Subscribing; 2 | 3 | public delegate void ComponentCallBack(TMessage message); 4 | public delegate Task AsyncComponentCallBack(TMessage message, CancellationToken ct); --------------------------------------------------------------------------------