Jul 7

Written by: Michael Washington
7/7/2019 1:42 PM  RssIcon

State Management in Blazor refers to the technique that you use to persist data between Blazor pages. Without state management, data would be lost.

State Management can be achieved by various methods including storing data in the database, or using packages such as Blazor-Fluxor.

In this article we will cover the AppState pattern that was introduced by the Microsoft Blazor team in the Blazing Pizza workshop.

 

The Issue – State Is Not Maintained

image

We start off with a Server side Blazor application.

We observe the code in the Counter.razor control by running the application, and navigating to that page.

 

image

When we click the Click me button, we see the Current count increase.

 

image

However, if we open the Index.razor page and add the following code, to add an instance of the Counter control:

 

<hr />
<Counter/>

 

image

When we run the application, we can increase the counter, by clicking the Click me button…

 

image

… however, when we navigate to the Counter page, that instance of the Counter returns to 0.

 

image

In addition, returning to the Index page, we see that it will also return the Current count to 0.

 

Implementing State Management

image

The first step is to add a class to hold the state.

We add a class called CounterState.cs using the following code:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BlazorStateManagement
{
    public class CounterState
    {
        public int CurrentCount { get; set; }
    }
}

 

image

Next we register this class, using Dependency Injection, by opening the Startup.cs file and adding the following code to the end of the ConfigureServices method:

 

            // ** SESSION STATE
            // Singleton usually means for all users, 
            // where as scoped means for the current unit-of-work
            services.AddScoped<CounterState>();

 

image

To consume the class, we open the Counter.razor page, and add the following code to the top of the code page:

 

@inject CounterState CounterState

 

We change the following code:

 

<p>Current count: @currentCount</p>

 

To:

 

<p>Current count: @CounterState.CurrentCount</p>

 

Finally we change the code section to the following:

 

@code {
    void IncrementCount()
    {
        // ** SESSION STATE
        int CurrentCount = CounterState.CurrentCount;
        CurrentCount++;
        // Set Current count on the Session State object
        CounterState.CurrentCount = CurrentCount;
    }
}

 

image

Now, when we run the application, we can increase the counter on the Index page…

 

image

… and the value is persisted on the Counter page.

 

Advanced State Management Using EventCallback

image

If we open the NavMenu.razor file and inject the state management class:

 

@inject CounterState CounterState

 

And the following markup:

 

<div>
    <p style="color:white">Counter State: @CounterState.CurrentCount</p>
</div>

 

image

When we run the application and increase the counter, we see that the count is not increased in the NavMenu.razor control…

 

image

…until we navigate to a new page.

Essentially, the values in the state are being tracked, but, that value will not necessarily display in another control automatically.

 

image

To resolve this, open the CounterState.cs file and replace the code of the class with the following code:

 

    public class CounterState
    {
        // _currentCount holds the current counter value
        // for the entire application
        private int _currentCount = 0;
        // StateChanged is an event handler other pages
        // can subscribe to 
        public event EventHandler StateChanged;
        // This method will always return the current count
        public int GetCurrentCount()
        {
            return _currentCount;
        }
        // This method will be called to update the current count
        public void SetCurrentCount(int paramCount)
        {
            _currentCount = paramCount;
            StateHasChanged();
        }
        // This method will allow us to reset the current count
        public void ResetCurrentCount()
        {
            _currentCount = 0;
            StateHasChanged();
        }
        
        private void StateHasChanged()
        {
            // This will update any subscribers
            // that the counter state has changed
            // so they can update themselves
            // and show the current counter value
            StateChanged?.Invoke(this, EventArgs.Empty);
        }
    }

 

Essentially we are creating an Event Handler that will allow other controls to subscribe to it, so that they will be notified when tracked values change. In addition, we are adding methods that can be consumed and invoked by other controls.

This allows us to centralize state logic code that is used in multiple places in our application.

 

Consume The Updated State Management Class

image

We now need to update the existing code to consume the updated State Management class.

Open the Counter.razor control and change all the code to the following:

 

@page "/counter"
@inject CounterState CounterState
<h1>Counter</h1>
<!-- We now call the GetCurrentCount() method -->
<!-- to get the current count -->
<p>Current count: @CounterState.GetCurrentCount()</p>
<button class="btn btn-primary"
        @onclick="@IncrementCount">
    Click me
</button>
<!-- Add a button to reset the current count -->
<!-- that calls the CounterState class directly -->
<button class="btn btn-primary"
        @onclick="@CounterState.ResetCurrentCount">
    Reset Count
</button>
@code {
    void IncrementCount()
    {
        // Call the GetCurrentCount() method
        // to get the current count
        int CurrentCount = CounterState.GetCurrentCount();
        // Increase the count
        CurrentCount++;
        // Set Current count on the Session State object
        CounterState.SetCurrentCount(CurrentCount);
    }
}

 

This demonstrates how a control can consume and invoke the new methods in the State Management class.

This control does not need to subscribe to the newly added Event Handler because this control will update itself automatically each time the button is clicked to increase the counter.

However, this is not necessarily true for other controls…

 

image

Next, open the NavMenu.razor control and change the DIV that display the counter state to the following:

 

<div>
    <!-- We now call the GetCurrentCount() method -->
    <!-- to get the current count -->
    <p style="color:white">
        Counter State: @CounterState.GetCurrentCount()
    </p>
</div>

 

This will display the latest value of the counter, however, it will still not update automatically.

We now need to add code to subscribe to the Event Handler we added to the State Management class.

 

Add the following code to the top of the control:

 

@implements IDisposable

 

This is added because we will add code, in the next step, that will properly dispose of the subscription we create, so that we do not have a memory leak.

Add the following code to the @code section of the page:

 

    // This method is called when the control is initialized
    protected override void OnInit()
    {
        // Subscribe to the StateChanged EventHandler
        CounterState.StateChanged +=
        OnCounterStateAdvancedStateChanged;
    }
    // This method is fired when the CounterState object
    // invokes its StateHasChanged() method
    // This will cause this control to invoke its own
    // StateHasChanged() method refreshing the page
    // and displaying the updated counter value
    void OnCounterStateAdvancedStateChanged(
        object sender, EventArgs e) => StateHasChanged();
    void IDisposable.Dispose()
    {
        // When this control is disposed of
        // unsubscribe from the StateChanged EventHandler
        CounterState.StateChanged -=
        OnCounterStateAdvancedStateChanged;
    }

 

image

Now, when we run the application, the counter value is always in sync.

We can also now reset the counter by clicking the Reset Count button.

 

Links

Blazor.net

Refactor State Management (Blazing Pizza Workshop)

Steve Sanderson discusses EventCallback and StateChanged in Blazor State Management

Creating A Step-By-Step End-To-End Database Server-Side Blazor Application

 

Download

The project is available at http://lightswitchhelpwebsite.com/Downloads.aspx

You must have Visual Studio 2019 Preview Edition (or higher) installed to run the code.

Tags: Blazor
Categories:

2 comment(s) so far...


Gravatar

Re: Implementing State Management In Blazor

Hello MIchael:

I am trying to add an AppState class to a Blazor Server Side application.

The problem that I have is that if you changes the url (page route) manually, then the ApState class is empty (all their values are empty).

Suppose that the url is:

https://localhost:44345/products/1

And then you manually changes to 2 in the browser address, once you do it, the AppState value are initialized.

Any idea why this happens.

By David on   7/12/2019 12:20 PM
Gravatar

Re: Implementing State Management In Blazor

@David - This will answer your question on how to handle that: https://docs.microsoft.com/en-us/aspnet/core/blazor/routing?view=aspnetcore-3.0#route-parameters

By Michael Washington on   7/12/2019 12:21 PM

Your name:
Gravatar Preview
Your email:
(Optional) Email used only to show Gravatar.
Your website:
Title:
Comment:
Security Code
CAPTCHA image
Enter the code shown above in the box below
Add Comment   Cancel 
Microsoft Visual Studio is a registered trademark of Microsoft Corporation / LightSwitch is a registered trademark of Microsoft Corporation