BlazorFlow 0.4.0
See the version list below for details.
dotnet add package BlazorFlow --version 0.4.0
NuGet\Install-Package BlazorFlow -Version 0.4.0
<PackageReference Include="BlazorFlow" Version="0.4.0" />
<PackageVersion Include="BlazorFlow" Version="0.4.0" />
<PackageReference Include="BlazorFlow" />
paket add BlazorFlow --version 0.4.0
#r "nuget: BlazorFlow, 0.4.0"
#:package BlazorFlow@0.4.0
#addin nuget:?package=BlazorFlow&version=0.4.0
#tool nuget:?package=BlazorFlow&version=0.4.0
BlazorFlow
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 injectingNavigationManager - ✅ 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
- Request Handlers - Execute before command processing
- Success Handlers - Execute after successful command execution
- 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>()andcontext.GetResponse<T>() - ✅ JSON Storage: Request/response automatically serialized and cleared after completion
- ✅ Access to Context: All handlers receive
RequestContextwithICommandContextfor 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 | Versions 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. |
-
net9.0
- Microsoft.AspNetCore.Components.Web (>= 9.0.9)
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 |