MgStateManagement 1.0.0

dotnet add package MgStateManagement --version 1.0.0
                    
NuGet\Install-Package MgStateManagement -Version 1.0.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="MgStateManagement" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="MgStateManagement" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="MgStateManagement" />
                    
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 MgStateManagement --version 1.0.0
                    
#r "nuget: MgStateManagement, 1.0.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 MgStateManagement@1.0.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=MgStateManagement&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=MgStateManagement&version=1.0.0
                    
Install as a Cake Tool

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: IAsyncAction extends IAction — 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: AddStateManagement scans 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

  1. Use records for state and actions — value equality and non-destructive mutation via with
  2. Keep state flat — avoid deep nesting; prefer composing smaller state objects
  3. One reducer class per domain — group related action handlers together
  4. Use effects for side effects — don't perform I/O inside reducers
  5. Register middleware in order — middlewares run in reverse registration order
  6. Pass CancellationToken to DispatchAsync when 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 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. 
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
1.0.0 104 6/21/2026