BlazorFlow 0.4.0

There is a newer prerelease version of this package available.
See the version list below for details.
dotnet add package BlazorFlow --version 0.4.0
                    
NuGet\Install-Package BlazorFlow -Version 0.4.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="BlazorFlow" Version="0.4.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="BlazorFlow" Version="0.4.0" />
                    
Directory.Packages.props
<PackageReference Include="BlazorFlow" />
                    
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 BlazorFlow --version 0.4.0
                    
#r "nuget: BlazorFlow, 0.4.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 BlazorFlow@0.4.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=BlazorFlow&version=0.4.0
                    
Install as a Cake Addin
#tool nuget:?package=BlazorFlow&version=0.4.0
                    
Install as a Cake Tool

BlazorFlow

NuGet License: MIT

A lightweight state management library for Blazor WebAssembly and Blazor Server applications implementing the Mediator pattern with CQRS support and async result tracking.

Features

  • Blazor Server & WASM Support: Works seamlessly with both hosting models
  • Thread-Safe Navigation: Navigate from handlers safely in both WASM and Server
  • Mediator Pattern: Decouples request handling from your Blazor components
  • CQRS Support: Separate command (write) and query (read) operations
  • Global Handlers: Add cross-cutting concerns (logging, validation, auditing) to all commands
  • Async State Tracking: Built-in loading, success, and error states
  • Reactive UI Updates: Automatic component re-rendering with state changes
  • Type-Safe: Full generic type support for request/response pairs
  • Cancellation Support: CancellationToken support throughout
  • Progress Updates: Handlers can notify UI during long-running operations
  • Auto-Discovery: Automatic handler registration via reflection
  • Thread-Safe Operations: Automatic UI thread marshaling for all operations

Installation

Install via NuGet Package Manager:

dotnet add package BlazorFlow --version 0.1.0-preview.1

Or via Package Manager Console:

Install-Package BlazorFlow -Version 0.1.0-preview.1

Quick Start

1. Register BlazorFlow in your Program.cs

using BlazorFlow.Extensions;
using System.Reflection;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");

// Register BlazorFlow with automatic handler discovery
builder.Services.AddBlazorFlowStateMediator(Assembly.GetExecutingAssembly());

await builder.Build().RunAsync();

2. Create a Command/Query

using BlazorFlow.Abstractions;

// Query that returns data
public record GetWeatherForecastQuery : IBlazorCommandRequest<WeatherForecast[]>;

// Command that performs an action
public record IncrementCounterCommand(int Amount) : IBlazorCommandRequest<int>;

3. Create a Handler

using BlazorFlow.Abstractions;

public class GetWeatherForecastHandler : IBlazorCommand<GetWeatherForecastQuery, WeatherForecast[]>
{
    public async Task<WeatherForecast[]> HandleAsync(
        GetWeatherForecastQuery request,
        ICommandContext context,
        CancellationToken cancellationToken = default)
    {
        // Simulate API call
        await Task.Delay(1000, cancellationToken);

        return new[]
        {
            new WeatherForecast(DateOnly.FromDateTime(DateTime.Now), 20, "Sunny")
        };
    }
}

4. Use in Your Blazor Component

@page "/weather"
@using BlazorFlow.Abstractions
@inject IStateMessageBus MessageBus

<h3>Weather Forecast</h3>

@if (weatherResult == null)
{
    <button @onclick="LoadWeather">Load Weather</button>
}
else if (weatherResult.IsLoading)
{
    <p>Loading...</p>
}
else if (weatherResult.IsSuccess && weatherResult.Value != null)
{
    <ul>
        @foreach (var forecast in weatherResult.Value)
        {
            <li>@forecast.Date: @forecast.TemperatureC°C - @forecast.Summary</li>
        }
    </ul>
}
else if (weatherResult.IsError)
{
    <div class="alert alert-danger">
        Error: @weatherResult.ErrorMessage
    </div>
}

@code {
    private IAsyncResult<WeatherForecast[]>? weatherResult;

    private void LoadWeather()
    {
        weatherResult = MessageBus.Publish<GetWeatherForecastQuery, WeatherForecast[]>(
            new GetWeatherForecastQuery());
        weatherResult.StateChanged += StateHasChanged;
    }
}

Advanced Features

Thread-Safe Navigation

Navigate from command handlers safely in both Blazor WASM and Server:

public class LoginHandler : IBlazorCommand<LoginCommand, LoginResult>
{
    public async Task<LoginResult> HandleAsync(
        LoginCommand request,
        ICommandContext context,
        CancellationToken cancellationToken = default)
    {
        // Authenticate user
        var user = await AuthenticateAsync(request.Username, request.Password);

        if (user != null)
        {
            // Thread-safe navigation - works in both WASM and Server!
            context.NavigateTo("/dashboard");
            return new LoginResult { Success = true };
        }

        return new LoginResult { Success = false, Error = "Invalid credentials" };
    }
}

Key Benefits:

  • No DI required: Use context.NavigateTo() instead of injecting NavigationManager
  • Thread-safe: Automatically marshaled to UI thread in Blazor Server
  • Works everywhere: Same code works in both WASM and Server
  • Background-safe: Call from async operations without threading issues

Real-Time Progress Updates

Handlers can notify the UI during long-running operations:

public class ProcessFilesHandler : IBlazorCommand<ProcessFilesCommand, FileProcessingResult>
{
    public async Task<FileProcessingResult> HandleAsync(
        ProcessFilesCommand request,
        ICommandContext context,
        CancellationToken cancellationToken = default)
    {
        for (int i = 0; i < files.Count; i++)
        {
            await ProcessFileAsync(files[i], cancellationToken);

            // Notify UI to re-render with progress
            context.StateHasChanged();
        }

        return new FileProcessingResult(files.Count);
    }
}

Reactive Callbacks

Chain operations with reactive callbacks:

var result = MessageBus.Publish<AddTodoCommand, TodoItem>(new AddTodoCommand("New Task"));
result.StateChanged += StateHasChanged;
result.OnSuccess(todo =>
{
    Console.WriteLine($"Todo added: {todo.Title}");
    LoadTodos(); // Refresh the list
});
result.OnError(ex =>
{
    Console.WriteLine($"Error: {ex.Message}");
});

Pattern Matching

Use the Match method for functional-style result handling:

var message = result.Match(
    onSuccess: value => $"Success: {value}",
    onError: ex => $"Error: {ex.Message}",
    onLoading: () => "Loading..."
);

Global Handlers (v0.3.0+)

NEW in v0.3.0: Add cross-cutting concerns like logging, validation, auditing, and error handling to all commands using global handlers.

Global handlers execute in a pipeline around your command handlers:

OnRequest → Main Handler → OnSuccess/OnError
Types of Global Handlers
  1. Request Handlers - Execute before command processing
  2. Success Handlers - Execute after successful command execution
  3. Error Handlers - Execute when commands throw exceptions
Registration

Register global handlers in Program.cs:

using BlazorFlow.Extensions;

builder.Services.AddBlazorFlowMediator(options =>
{
    // Add global request handlers (execute before command)
    options.AddRequestHandler<LoggingRequestHandler>();
    options.AddRequestHandler<ValidationRequestHandler>();

    // Add global success handlers (execute after success)
    options.AddSuccessHandler<AuditingSuccessHandler>();

    // Add global error handlers (execute on errors)
    options.AddErrorHandler<ErrorNotificationHandler>();

}, Assembly.GetExecutingAssembly());
Example: Logging Handler
public class LoggingRequestHandler : IBlazorCommandRequestHandler
{
    private readonly ILogger<LoggingRequestHandler> _logger;

    public LoggingRequestHandler(ILogger<LoggingRequestHandler> logger)
    {
        _logger = logger;
    }

    public Task OnRequestAsync(RequestContext context, CancellationToken cancellationToken = default)
    {
        _logger.LogInformation("Executing command: {RequestType} at {Timestamp}",
            context.RequestType?.Name ?? "Unknown",
            context.Timestamp);

        // Access request data if needed:
        // var request = context.GetRequest<YourRequestType>();

        return Task.CompletedTask;
    }
}
Example: Error Notification Handler
public class ErrorNotificationHandler : IBlazorCommandErrorHandler
{
    private readonly ILogger<ErrorNotificationHandler> _logger;

    public ErrorNotificationHandler(ILogger<ErrorNotificationHandler> logger)
    {
        _logger = logger;
    }

    public Task OnErrorAsync(RequestContext context, CancellationToken cancellationToken = default)
    {
        _logger.LogError(context.Exception, "Command {CommandType} failed",
            context.RequestType?.Name ?? "Unknown");

        // Access request data if needed:
        // var request = context.GetRequest<YourRequestType>();

        // You could also publish error notifications to the UI here
        return Task.CompletedTask;
    }
}
Key Features
  • Full DI Support: All handlers resolved from dependency injection
  • Simple Non-Generic Design: No complex generic type registrations needed
  • RequestContext API: Access request/response data via context.GetRequest<T>() and context.GetResponse<T>()
  • JSON Storage: Request/response automatically serialized and cleared after completion
  • Access to Context: All handlers receive RequestContext with ICommandContext for UI operations
  • Error Isolation: Failed global handlers don't break the pipeline
  • Order Preservation: Handlers execute in registration order

Architecture

BlazorFlow implements the Mediator pattern with CQRS principles:

Blazor Component
    ↓ (Publishes request)
IStateMessageBus
    ↓ (Routes to handler)
IBlazorCommand Handler
    ↓ (Executes logic)
IAsyncResult<T>
    ↓ (Tracks state: Loading → Success/Error)
Blazor Component (Re-renders via StateChanged event)

Documentation

For more examples and detailed documentation, see the samples directory.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Roadmap

  • Multi-targeting support (net8.0, net9.0)
  • Blazor Server support (v0.2.0+)
  • Pipeline behaviors / Global handlers (v0.3.0+)
  • Request/response interceptors via global handlers (v0.3.0+)
  • Handler result caching
  • Performance optimizations

Note: This is a preview release. APIs may change in future versions.

Product Compatible and additional computed target framework versions.
.NET 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.4.1-preview 233 10/31/2025
0.4.0 191 10/31/2025
0.3.0-preview.4 143 10/31/2025
0.3.0-preview.1 158 10/29/2025
0.2.0-preview.1 137 10/26/2025
0.1.0-preview.1 149 10/26/2025