Introduction

I recently started to experiment with the Blazor Webassembly project template a bit more. Like with all Single Page Application projects you will need to have some strategy for state management rather sooner than later.

The Fluxor library is a Flux/Redux library for .NET which might be an excellent weapon of choice when you are already familliar with the Flux approach or just want a solution out of the box.

In a previous blog post I used the Observable State Store pattern to add home brew state management in an Angular SPA. So why not take this approach and implement a .NET version for it which we can use in Blazor WASM ?

Observable State Store

The Observable State Store is basically a centralized store in which we store state data. All operations that are modifying the state are performed with actions on the state store. So actions are the only way to change the state.

Components that want to be notified about state changes can subscribe themselves to the state store. In the Angular version I used RXJS, so in C# we can just use the original Reactive Extensions library RX.NET for it.

Prerequisites

  • Create a default Blazor WASM application, using the regular Visual Studio project template or .NET CLI dotnet new blazorwasm

  • As we will use RX.NET we will have to install this package from NuGet. dotnet add package System.Reactive --version 5.0.0

State Store base implementation

I created a base class for state stores and a concrete implementation for holding the state of a counter so I could take a default Blazor WASM template and refactored the counter part of it to use the Observable State Pattern as an example.

The state store base is implemented as:

public class StateStoreBase<T> where T: class
{  
    protected BehaviorSubject<T> state;
    
    public StateStoreBase() =>  state = new BehaviorSubject<T>(default(T)!);
    
    public IObservable<T> Value => state.AsObservable();
}
  • The generic type T is the type that holds the state in the state store and can be just any class/record.

  • Inside the state store we use the RX BehaviourSubject<T> internally to publish new values for the state.

  • The state values are exposed as a public IObservable<T> so we can subscribe on it for change notifications.

Counter State Store

To hold the counter value I created this record: public record CounterState(int counter);

The concrete Counter State Store is implemented as:

public class CounterStateStore : StateStoreBase<CounterState>, ICounterStateStoreActions
{
    public CounterStateStore() : base()
    {
        CounterState initialState = new CounterState(0);
        this.state = new BehaviorSubject<CounterState>(initialState);
    }
    public void Increment()
    {
        var nextState =  this.state.Value with { counter =  this.state.Value.counter +1}; 
        this.state.OnNext(nextState);  
    }
}

All possible actions that can change the counter state are defined in the interface ICounterStateStoreActions.

public interface ICounterStateStoreActions
{   
    void Increment();
}

The implementation of the action is done directly in the Counter State Store and it creates a new record object which is published to the BehaviourSubject: this.state.OnNext(nextState).

Using the State Store

We want to be able to inject the state store into Blazor components. To be able to use the Dependency Injection system from Blazor we will have to register the counter state store scoped as singleton in Program.cs.

builder.Services.AddSingleton<CounterStateStore>();

This counter.razor component injects the counter state store and it is used to call the Increment() action and subscribed to change notifications to update the UI whenever the state changes.

@inject CounterStateStore counterStateStore;

@code {
    public int currentCount;
    private IDisposable? subscription;

    protected override void OnInitialized()
    {
        this.subscription = this.counterStateStore.Value
          .Subscribe((state) => 
          {
              currentCount = state.counter;
          });
    }
    private void IncrementCount()
    {
        counterStateStore.Increment();
    }

    public void Dispose()
    {
        subscription?.Dispose();        
    }
}
  • The p tag binds against the public currentCount field.

  • The button binds against the IncrementCount() method.

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

Summary

Using the Observable State Store Pattern in Blazor Webassembly is made easy by using the Reactive Extensions library (RX.NET). The solution itself is quite straight forward and extensible. So when you feel using a Flux library for state management is bit of an overkill it is not hard to come up with a home brew solution that might be elegant as well. I hope this post can give you some inspiration.

The full example of the Observable State Store in Blazor Webassembly is available in this GitHub repository