This post is a small tip if you are dealing with managing and persisting states in your Blazor app.

Jeremy Likness made a good post covering the challenges with maintaining the state of your component and what options you have to solve it.

An obvious example here is when you want to maintain the data in your form. When you are filling out a form, and then navigate away from the page, it would be convenient in most cases (depending on the scenario), that the data you filled out is not gone.

Looking at the Counter component from the template app, then when you navigate away, the counter is reset. That could pose a bad user experience.

One way of circumventing this described by Jeremy, is by utilizing dependency injection and service registration. So instead of binding your viewmodel directly to your form, or using local variables like in the counter example, you can inject the model as a service - either singleton, scoped or transient. That way the data set is maintained in memory within the scope of your service registration.

But what if  you have tons of viewmodels or tons of pages you all want to persist state on? Then you would need a service registration for each one of them.

No, generics to the rescue!

I made a small sample app you can check out:

https://github.com/MortenMeisler/Blazor.StateManagement/

The idea is to make a generic service of type T and then instantiate the model when the service gets injected:

public class StateManager<T> : IStateManager<T> where T : class, new()
    {
        public T Model { get; set; }
        public StateManager()
        {
            //equivalent of doing new T(), but with improvements: See:
            //https://devblogs.microsoft.com/premier-developer/dissecting-the-new-constraint-in-c-a-perfect-example-of-a-leaky-abstraction/
            Model = Activator.CreateInstance<T>();
        }
    }

Then in your program.cs you only need to register this service:

builder.Services.AddScoped(typeof(IStateManager<>), typeof(StateManager<>));

And use it in your components:

@page "/myformsavestate"
@inject IStateManager<MyCrazyViewModel> stateManager

<EditForm EditContext="@_editContext" OnValidSubmit="HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <div class="row">
        <div class="col-md-6">
            <div class="form-group">
                <label for="FirstName" class="control-label">First Name</label>
                <InputText id="name" class="form-control" @bind-Value="stateManager.Model.FirstName"></InputText>
            </div>
            <div class="form-group">
                <label for="FirstName" class="control-label">Last Name</label>
                <InputText id="name" class="form-control" @bind-Value="stateManager.Model.LastName"></InputText>
            </div>
            <div class="form-group">
                <label for="FirstName" class="control-label">Description</label>
                <InputText id="name" class="form-control" @bind-Value="stateManager.Model.Description"></InputText>
            </div>
            <div class="form-group">
                <label for="DateOfBirth" class="control-label">Date of Birth</label>
                <InputDate id="name" class="form-control" @bind-Value="stateManager.Model.DateOfBirth"></InputDate>
            </div>
            <div class="form-group">
                <label for="PetNames" class="control-label">Select Pet Name</label>
                <select id="Location" @bind="@stateManager.Model.PetName" class="form-control">
                    <option></option>
                    @foreach (string petName in @petNames)
                    {
                        <option value="@petName">@petName</option>
                    }
                  
                </select>
            </div>

        </div>
    </div>
    <input type="submit" class="btn btn-default" value="Submit" />
</EditForm>

@if (_isValid)
{
    <p><em>First Name: @stateManager.Model.FirstName</em></p>
    <p><em>Last Name: @stateManager.Model.FirstName</em></p>
    <p><em>Description: @stateManager.Model.Description</em></p>
    <p><em>Date of Birth: @stateManager.Model.DateOfBirth</em></p>
    <p><em>PetName selected: @stateManager.Model.PetName</em></p>

}

@code {

    private EditContext _editContext;
    private bool _isValid;
    private IList<string> petNames = new List<string>() { "Molly", "Max", "Mr. Something", "Tiger king" };


    protected override void OnInitialized()
    {
        _editContext = new EditContext(stateManager.Model);
    }

    private void HandleValidSubmit()
    {
        _isValid = _editContext.Validate();

        //Do stuff - create instance with API or something
    }
}

That's it. Now when you navigate away from the form and back, the values in the fields are still there.

Do notice, that the state is only persisted in memory. So if you refresh the page the data will be gone. To solve that you would need to save it in the browser storage described by Jeremy.