Chaos.BlazorMessageBus 0.3.0

dotnet add package Chaos.BlazorMessageBus --version 0.3.0
                    
NuGet\Install-Package Chaos.BlazorMessageBus -Version 0.3.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Chaos.BlazorMessageBus" Version="0.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Chaos.BlazorMessageBus" Version="0.3.0" />
                    
Directory.Packages.props
<PackageReference Include="Chaos.BlazorMessageBus" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Chaos.BlazorMessageBus --version 0.3.0
                    
#r "nuget: Chaos.BlazorMessageBus, 0.3.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Chaos.BlazorMessageBus@0.3.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Chaos.BlazorMessageBus&version=0.3.0
                    
Install as a Cake Addin
#tool nuget:?package=Chaos.BlazorMessageBus&version=0.3.0
                    
Install as a Cake Tool

BlazorMessageBus

A lightweight, thread-safe message bus for Blazor applications. BlazorMessageBus enables decoupled communication between components and services using a publish/subscribe pattern.

GitHub License NuGet Version NuGet Downloads GitHub last commit GitHub Actions Workflow Status

Features

  • Publish/Subscribe: Send messages of any type and subscribe with type-safe handlers.
  • Async and Sync Handlers: Supports both synchronous and asynchronous message handlers.
  • Scoped Message Exchange: Simplifies subscription management for Blazor components.
  • Thread-Safe: Safe for concurrent publishing and subscribing.
  • Flexible Error Handling: Configurable exception handling and fail-fast options.
  • Dependency Injection: Easy integration with ASP.NET Core DI.

Installation

Add the NuGet package:

dotnet add package Chaos.BlazorMessageBus

Getting Started

  1. Register the services in your Program.cs:
builder.Services.AddBlazorMessageBus(options =>
{
    options.StopOnFirstError = false;
    options.OnPublishException = ex => { /* log or handle */ return Task.CompletedTask; };
    options.OnBridgeException = ex => { /* log bridge errors */ return Task.CompletedTask; };
});
  1. Inject and use the message bus in your components or services:
@inject IBlazorMessageBus MessageBus

// Subscribe
var subscription = MessageBus.Subscribe<string>(msg => Console.WriteLine(msg));

// Publish
await MessageBus.PublishAsync("Hello, world!");

// Dispose when done
subscription.Dispose();

For cleanup best practices, see Disposal and Lifecycle Guidance.

  1. For Blazor components, use IBlazorMessageExchange for automatic cleanup:
@inject IBlazorMessageExchange MessageExchange

protected override void OnInitialized()
{
    MessageExchange.Subscribe<string>(msg => { /* handle message */ });
}

public void Dispose()
{
    MessageExchange.Dispose();
}

For cleanup best practices, see Disposal and Lifecycle Guidance.

Configuration Options

  • StopOnFirstError (Boolean): If true, publishing stops on the first handler exception. If false, all handlers are invoked and exceptions are aggregated.
  • OnPublishException (Func<Exception, Task>): Optional async handler for exceptions thrown by subscribers.
  • OnBridgeException (Func<Exception, Task>): Optional async handler for exceptions thrown during message bridging (e.g., filter predicate or outbound transport handler). Exceptions thrown by this callback are swallowed.

Advanced Usage

Handling Multiple Message Types

You can subscribe to and publish any type:

MessageBus.Subscribe<int>(i => Console.WriteLine($"Int: {i}"));
MessageBus.Subscribe<MyCustomEvent>(evt => HandleCustomEvent(evt));

await MessageBus.PublishAsync(42);
await MessageBus.PublishAsync(new MyCustomEvent { ... });

Asynchronous Handlers

MessageBus.Subscribe<string>(async msg =>
{
    await SomeAsyncOperation(msg);
});

Exception Handling and Aggregation

If multiple handlers throw exceptions during publish, all exceptions are aggregated and thrown as an AggregateException (unless StopOnFirstError is enabled):

try
{
    await MessageBus.PublishAsync("Test");
}
catch (AggregateException ex)
{
    foreach (var inner in ex.InnerExceptions)
    {
        // Handle each exception
    }
}

Thread Safety

BlazorMessageBus is thread-safe. You can safely publish and subscribe from multiple threads concurrently.

Unsubscribing

Dispose the returned subscription to unsubscribe:

var subscription = MessageBus.Subscribe<string>(...);
subscription.Dispose();

Disposal and Lifecycle Guidance

  • IBlazorMessageExchange must be disposed with the owning component’s lifecycle. Implement IDisposable or IAsyncDisposable on the component and dispose the exchange in Dispose()/DisposeAsync().
  • Subscriptions returned from Subscribe(...) implement IDisposable. Disposing a subscription stops further message delivery; there is no separate “Unsubscribe”.
  • Scope subscriptions to the component that created them. Prefer per-component exchanges over long-lived, global subscriptions.
  • Treat disposal as idempotent and safe to call multiple times from a consumer perspective. After disposal, do not expect further callbacks; in-flight callbacks may complete, but new ones should not be scheduled.
  • Avoid blocking in disposal paths. Do not wait on asynchronous work during disposal; release references and let in-flight operations complete naturally.
  • For Blazor components, create the exchange during initialization and keep a field reference; dispose it in the component’s Dispose()/DisposeAsync() so all managed subscriptions are also cleaned up.

Message Bridging

BlazorMessageBus supports bridging messages across bus instances via IBlazorMessageBus.CreateMessageBridge(...). A bridge can forward outbound messages to any transport (HTTP, SignalR, etc.) and inject inbound messages into the local bus.

  • Forwarding model: Forwarding to bridges is fire-and-forget and does not affect PublishAsync latency. Remote delivery is eventual and may complete after PublishAsync returns.
  • Loop prevention: Each bridge has a unique Id. Inbound injections carry the originating bridge Id so the local bus can skip forwarding back to that same bridge, preventing loops.
  • Error isolation: Exceptions thrown during forwarding are swallowed so local delivery is never affected. To observe such errors, configure options.OnBridgeException.
  • Lifecycle and disposal: Disposing a bridge deactivates it, stops further forwarding, forbids additional injections, and deregisters it from the bus. Disposal is idempotent and never throws.
  • Filtering: Configure filters to control which message types are forwarded using BlazorMessageBridgeFilters.Include(...), Exclude(...), or Where(...). Filters can be reconfigured at runtime and apply to subsequent messages.
  • Cancellation: Cancellation tokens passed to PublishAsync are observed only by outbound forwarding (e.g., your transport). Local delivery is not canceled. Similarly, InjectMessageAsync(..., cancellationToken) does not cancel local delivery.
  • Topologies and duplicates: When multiple paths exist to a destination (e.g., A→C direct and A→B→C), downstream buses may observe duplicates. Apply filters or deduplication at the edges if needed.

Bridging Example

This example shows how to forward messages from App A to App B using a custom transport (e.g., SignalR/HTTP). The outbound side sends to your transport; the inbound side injects into the local bus.

// App A (outbound)
// IBlazorMessageBus busA is resolved from DI

// Create a bridge and wire outbound forwarding to your transport
var bridgeToB = busA.CreateMessageBridge(async (Type type, Object payload, CancellationToken ct) =>
{
    await transportAtoB.SendAsync(type, payload, ct); // implement this (SignalR/HTTP/etc.)
});

// Optionally filter which types are forwarded
bridgeToB.ConfigureFilter(BlazorMessageBridgeFilters.Include(typeof(String)));

// App B (inbound)
// IBlazorMessageBus busB is resolved from DI

// Create a local bridge instance to own the inbound injection identity (prevents loops)
var inboundBridge = busB.CreateMessageBridge((_, _, _) => Task.CompletedTask);

// When your transport receives a message from App A, inject it into App B
transportAtoB.OnReceived(async (Type type, Object payload, CancellationToken ct) =>
{
    await inboundBridge.InjectMessageAsync(type, payload, ct);
});

// Subscriptions on App B receive injected messages
busB.Subscribe<String>(msg => Console.WriteLine($"App B received: {msg}"));

// Publish on App A: delivered locally and forwarded to App B
await busA.PublishAsync("Hello from A");

// Cleanup when done
bridgeToB.Dispose();
inboundBridge.Dispose();

Monitoring Bridging Errors

To observe forwarding failures without impacting local delivery, configure OnBridgeException:

// Program.cs
builder.Services.AddBlazorMessageBus(options =>
{
    options.OnBridgeException = ex =>
    {
        // Replace with your logging/telemetry of choice
        Console.Error.WriteLine($"[BridgeError] {ex}");
        return Task.CompletedTask;
    };
});

The callback is invoked when the bridge filter predicate or the outbound transport handler throws. Exceptions thrown by the callback itself are swallowed to avoid cascading failures.

License

MIT License - see LICENSE for more information.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.3.0 131 8/10/2025
0.2.0 112 8/3/2025
0.1.1 48 8/2/2025
0.1.0 24 8/2/2025

# v0.3.0 (2025-08-10)

### ✨ Features

- Add support for bridging messages between multiple MessageBus instances (#26) by @chA0s-Chris
- Add support for type-based non-generic subscriptions and publishing (#27) by @chA0s-Chris

### 📚 Documentation

- Add disposal and lifecycle guidance to README.md (#28) by @chA0s-Chris