MgStateManagement 1.0.0
dotnet add package MgStateManagement --version 1.0.0
NuGet\Install-Package MgStateManagement -Version 1.0.0
<PackageReference Include="MgStateManagement" Version="1.0.0" />
<PackageVersion Include="MgStateManagement" Version="1.0.0" />
<PackageReference Include="MgStateManagement" />
paket add MgStateManagement --version 1.0.0
#r "nuget: MgStateManagement, 1.0.0"
#:package MgStateManagement@1.0.0
#addin nuget:?package=MgStateManagement&version=1.0.0
#tool nuget:?package=MgStateManagement&version=1.0.0
MgStateManagement
A lightweight, predictable state management library for .NET 10 applications — including Blazor WebAssembly, Blazor Server, and console apps — inspired by Redux.
Features
- Predictable State Management: Actions and reducers for deterministic state updates
- Unified Action Model:
IAsyncActionextendsIAction— async actions are first-class actions - Async Support: Full async/await support for reducers, effects, and dispatch with
CancellationToken - Effects: Side-effect handlers that can dispatch further actions after state is updated
- Middleware Pipeline: Extensible pipeline for logging, error handling, immutability, and more
- Blazor Integration:
StateComponentBase<TState>re-renders automatically on state changes - Dependency Injection: Auto-discovers and registers reducers and effects from your assembly
- Auto-discovery:
AddStateManagementscans the entry assembly (with calling-assembly fallback) — no manual registration required for most cases
Installation
<PackageReference Include="MgStateManagement" Version="1.0.0" />
Quick Start
1. Define Your State
Use record types for value equality and immutability:
public record AppState
{
public int Counter { get; init; } = 0;
public string Message { get; init; } = string.Empty;
public bool IsLoading { get; init; } = false;
public List<string> Items { get; init; } = [];
}
2. Define Actions
// Synchronous actions implement IAction
public record IncrementCounterAction : IAction;
public record DecrementCounterAction : IAction;
public record SetMessageAction(string Message) : IAction;
// Async actions implement IAsyncAction (which extends IAction)
public record LoadItemsAction : IAsyncAction;
3. Create Reducers
public class CounterReducer :
IReducer<AppState, IncrementCounterAction>,
IReducer<AppState, DecrementCounterAction>
{
public AppState Reduce(AppState state, IncrementCounterAction action) =>
state with { Counter = state.Counter + 1 };
public AppState Reduce(AppState state, DecrementCounterAction action) =>
state with { Counter = state.Counter - 1 };
}
public class ItemsReducer : IAsyncReducer<AppState, LoadItemsAction>
{
public async Task<AppState> ReduceAsync(AppState state, LoadItemsAction action)
{
await Task.Delay(500); // simulate API call
return state with { Items = ["Item 1", "Item 2", "Item 3"], IsLoading = false };
}
}
Reducers are auto-discovered by AddStateManagement — no manual registration needed.
4. Configure Services
// Program.cs (Blazor WASM)
builder.Services.AddStateManagement(new AppState());
// Optional built-in middlewares
builder.Services.AddMiddleware<AppState, LoggingMiddleware<AppState>>();
builder.Services.AddMiddleware<AppState, ErrorHandlingMiddleware<AppState>>();
5. Use in Blazor Components
@page "/counter"
@using MgStateManagement.Blazor
@inherits StateComponentBase<AppState>
<h3>Counter: @State.Counter</h3>
<button @onclick="() => Dispatch(new IncrementCounterAction())">+</button>
<button @onclick="() => Dispatch(new DecrementCounterAction())">-</button>
<button @onclick="LoadItems">Load Items</button>
@code {
private async Task LoadItems() =>
await DispatchAsync(new LoadItemsAction());
}
StateComponentBase<TState> subscribes to StateChanged and calls StateHasChanged() automatically.
Core Concepts
Actions
| Interface | Use for |
|---|---|
IAction |
Synchronous, in-memory state changes |
IAsyncAction |
API calls, I/O, or any async work. Extends IAction. |
Reducers
| Interface | Use for |
|---|---|
IReducer<TState, TAction> |
Synchronous state reduction |
IAsyncReducer<TState, TAction> |
Async state reduction |
Reducers must return a new state instance (use with expressions on records).
Effects
Effects run after state is updated and can dispatch further actions.
public class LogEffect : IAsyncEffect<AppState, LoadItemsAction>
{
public async Task HandleAsync(
AppState state,
LoadItemsAction action,
IDispatcher dispatcher,
CancellationToken ct = default)
{
await LogToServer(state, ct);
// Can dispatch follow-up actions via dispatcher
}
}
Effects are also auto-discovered by AddStateManagement.
Middleware
Middleware wraps the dispatch pipeline and runs before the reducer:
public class AuthMiddleware<TState> : IMiddleware<TState> where TState : class
{
public async Task<TState> HandleAsync<TAction>(
TState state,
TAction action,
Func<TState, TAction, Task<TState>> next) where TAction : class
{
if (action is IRequiresAuth && !IsAuthenticated())
throw new UnauthorizedAccessException();
return await next(state, action);
}
}
Middlewares execute in reverse registration order (last registered = first to run).
CancellationToken Support
DispatchAsync and effect handlers accept an optional CancellationToken:
await StateStore.DispatchAsync(new LoadItemsAction(), cancellationToken);
Built-in Middlewares
LoggingMiddleware<TState>
Logs the action name and elapsed processing time via ILogger.
ErrorHandlingMiddleware<TState>
Catches exceptions thrown by reducers and returns the unchanged state, preventing app crashes.
ImmutabilityMiddleware<TState>
Deep-copies the state via JSON serialisation before passing it to the reducer, ensuring reducers always receive a fresh instance.
Advanced Usage
Override State Change Hook
@inherits StateComponentBase<AppState>
@code {
protected override Task OnStateChangedAsync(AppState newState)
{
Console.WriteLine($"Counter is now {newState.Counter}");
return Task.CompletedTask;
}
}
Direct Store Injection
@inject IStateStore<AppState> StateStore
@implements IDisposable
@code {
protected override void OnInitialized()
{
StateStore.StateChanged += OnStateChanged;
}
private void OnStateChanged(AppState newState) =>
InvokeAsync(StateHasChanged);
public void Dispose() =>
StateStore.StateChanged -= OnStateChanged;
}
Manual Reducer / Effect Registration
Auto-discovery covers the most common case. For library or test projects where the entry assembly may differ, you can register explicitly:
services.AddReducer<CounterReducer>();
services.AddEffect<LogEffect>();
Best Practices
- Use records for state and actions — value equality and non-destructive mutation via
with - Keep state flat — avoid deep nesting; prefer composing smaller state objects
- One reducer class per domain — group related action handlers together
- Use effects for side effects — don't perform I/O inside reducers
- Register middleware in order — middlewares run in reverse registration order
- Pass
CancellationTokentoDispatchAsyncwhen calling from UI events or request handlers
API Reference
IStateStore<TState>
| Member | Description |
|---|---|
State |
Current state snapshot |
StateChanged |
Event raised after every state update |
Dispatch<TAction>(action) |
Dispatch a synchronous action |
DispatchAsync<TAction>(action, ct) |
Dispatch an async action with optional cancellation |
StateComponentBase<TState>
| Member | Description |
|---|---|
State |
Current state (shortcut to StateStore.State) |
Dispatch<TAction>(action) |
Dispatch a synchronous action |
DispatchAsync<TAction>(action) |
Dispatch an async action |
OnStateChangedAsync(newState) |
Override to react to state changes |
Service Extensions
| Method | Description |
|---|---|
AddStateManagement<TState>(initialState) |
Registers the store and auto-discovers reducers/effects |
AddReducer<TReducer>() |
Manually registers a reducer |
AddEffect<TEffect>() |
Manually registers an effect |
AddMiddleware<TState, TMiddleware>() |
Registers middleware |
License
MIT License - see LICENSE file for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. 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. |
-
net10.0
- Microsoft.AspNetCore.Components.Web (>= 9.0.0)
- Microsoft.Extensions.DependencyInjection (>= 9.0.0)
- Microsoft.Extensions.Logging (>= 9.0.0)
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 |
|---|---|---|
| 1.0.0 | 104 | 6/21/2026 |